Skip to content

Commit 51b595f

Browse files
authored
Add GCP resource detection module (#1162)
1 parent dd81618 commit 51b595f

File tree

9 files changed

+728
-0
lines changed

9 files changed

+728
-0
lines changed

.github/component_owners.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@ components:
2727
disk-buffering:
2828
- LikeTheSalad
2929
- zeitlinger
30+
gcp-resources:
31+
- jsuereth
32+
- psx95
3033
jfr-connection:
3134
- breedx-splk
3235
- jeanbisutti

.github/scripts/draft-change-log-entries.sh

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ component_names["aws-xray/"]="AWS X-Ray SDK support"
2929
component_names["aws-xray-propagator/"]="AWS X-Ray propagator"
3030
component_names["consistent-sampling/"]="Consistent sampling"
3131
component_names["disk-buffering/"]="Disk buffering"
32+
component_names["gcp-resources/"]="GCP Resources"
3233
component_names["jfr-connection/"]="JFR connection"
3334
component_names["jfr-events/"]="JFR events"
3435
component_names["jmx-metrics/"]="JMX metrics"

gcp-resources/README.md

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# GCP Resource Detectors for OpenTelemetry
2+
3+
This module provides GCP resource detectors for OpenTelemetry.
4+
5+
The following OpenTelemetry semantic conventions will be detected:
6+
7+
| Resource attribute | GCE | GKE | GCR | GCF | GAE |
8+
| ------------------ | --- | --- | --- | --- | --- |
9+
| cloud.platform | gcp_compute_engine | gcp_kubernetes_engine | gcp_cloud_run | gcp_cloud_run | gcp_app_engine |
10+
| cloud.provider | gcp | gcp | gcp | gcp | gcp |
11+
| cloud.account.id | auto | auto | auto | auto | auto |
12+
| cloud.availability_zone | auto | auto | auto | auto | auto |
13+
| cloud.region | auto | auto | auto | auto | auto |
14+
| host.id | auto | auto | | | |
15+
| host.name | auto | auto | | | |
16+
| host.type | auto | auto | | | |
17+
| k8s.pod.name | | downward API or auto | | | |
18+
| k8s.namespace.name | | downward API | | | |
19+
| k8s.container.name | | hardcoded (manual) | | | |
20+
| k8s.cluster.name | | auto | | | |
21+
| faas.name | | | auto | auto | auto |
22+
| faas.version | | | auto | auto | auto |
23+
| faas.instance | | | auto | auto | auto |
24+
25+
## Downward API
26+
27+
For GKE applications, some values must be passed via the environment variable using k8s
28+
"downward API". For example, the following spec will ensure `k8s.namespace.name` and
29+
`k8s.pod.name` are correctly discovered:
30+
31+
```yaml
32+
spec:
33+
containers:
34+
- name: my-application
35+
image: gcr.io/my-project/my-image:latest
36+
env:
37+
- name: POD_NAME
38+
valueFrom:
39+
fieldRef:
40+
fieldPath: metadata.name
41+
- name: NAMESPACE
42+
valueFrom:
43+
fieldRef:
44+
fieldPath: metadata.namespace
45+
- name: CONTAINER_NAME
46+
value: my-application
47+
```
48+
49+
Additionally, the container name will only be discovered via the environment variable `CONTAINER_NAME`
50+
which much be included in the environment.
51+
52+
## Component Owners
53+
54+
- [Josh Suereth](https://github.com/jsuereth), Google
55+
- [Pranav Sharma](https://github.com/psx95), Google
56+
57+
Learn more about component owners in [component_owners.yml](../.github/component_owners.yml).

gcp-resources/build.gradle.kts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
plugins {
2+
id("otel.java-conventions")
3+
4+
id("otel.publish-conventions")
5+
}
6+
7+
description = "OpenTelemetry GCP Resources Support"
8+
otelJava.moduleName.set("io.opentelemetry.contrib.gcp.resource")
9+
10+
dependencies {
11+
api("io.opentelemetry:opentelemetry-api")
12+
api("io.opentelemetry:opentelemetry-sdk")
13+
14+
// Provides GCP resource detection support
15+
implementation("com.google.cloud.opentelemetry:detector-resources-support:0.27.0")
16+
17+
implementation("io.opentelemetry.semconv:opentelemetry-semconv")
18+
19+
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
20+
21+
implementation("com.fasterxml.jackson.core:jackson-core")
22+
23+
testImplementation("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
24+
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
25+
26+
testImplementation("org.mockito:mockito-core")
27+
testImplementation("com.google.guava:guava")
28+
29+
testImplementation("org.junit.jupiter:junit-jupiter-api")
30+
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
31+
}

gcp-resources/gradle.properties

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# TODO: uncomment when ready to mark as stable
2+
# otel.stable=true
Lines changed: 253 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,253 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.gcp.resource;
7+
8+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_APP_VERSION;
9+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_AVAILABILITY_ZONE;
10+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_CLOUD_REGION;
11+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_INSTANCE_ID;
12+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_MODULE_NAME;
13+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_AVAILABILITY_ZONE;
14+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_CLOUD_REGION;
15+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_INSTANCE_HOSTNAME;
16+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_INSTANCE_ID;
17+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_INSTANCE_NAME;
18+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_MACHINE_TYPE;
19+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_CLUSTER_LOCATION;
20+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_CLUSTER_LOCATION_TYPE;
21+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_CLUSTER_NAME;
22+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_HOST_ID;
23+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_LOCATION_TYPE_REGION;
24+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_LOCATION_TYPE_ZONE;
25+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_AVAILABILITY_ZONE;
26+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_CLOUD_REGION;
27+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_INSTANCE_ID;
28+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_NAME;
29+
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_REVISION;
30+
31+
import com.google.cloud.opentelemetry.detection.DetectedPlatform;
32+
import com.google.cloud.opentelemetry.detection.GCPPlatformDetector;
33+
import io.opentelemetry.api.common.Attributes;
34+
import io.opentelemetry.api.common.AttributesBuilder;
35+
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
36+
import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider;
37+
import io.opentelemetry.sdk.resources.Resource;
38+
import io.opentelemetry.semconv.ResourceAttributes;
39+
import java.util.Map;
40+
import java.util.Optional;
41+
import java.util.logging.Logger;
42+
43+
public class GCPResourceProvider implements ResourceProvider {
44+
45+
private static final Logger LOGGER = Logger.getLogger(GCPResourceProvider.class.getSimpleName());
46+
private final GCPPlatformDetector detector;
47+
48+
// for testing only
49+
GCPResourceProvider(GCPPlatformDetector detector) {
50+
this.detector = detector;
51+
}
52+
53+
public GCPResourceProvider() {
54+
this.detector = GCPPlatformDetector.DEFAULT_INSTANCE;
55+
}
56+
57+
/**
58+
* Generates and returns the attributes for the resource. The attributes vary depending on the
59+
* type of resource detected.
60+
*
61+
* @return The {@link Attributes} for the detected resource.
62+
*/
63+
public Attributes getAttributes() {
64+
DetectedPlatform detectedPlatform = detector.detectPlatform();
65+
if (detectedPlatform.getSupportedPlatform()
66+
== GCPPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM) {
67+
return Attributes.empty();
68+
}
69+
70+
// This is running on some sort of GCPCompute - figure out the platform
71+
AttributesBuilder attrBuilder = Attributes.builder();
72+
attrBuilder.put(ResourceAttributes.CLOUD_PROVIDER, ResourceAttributes.CloudProviderValues.GCP);
73+
attrBuilder.put(ResourceAttributes.CLOUD_ACCOUNT_ID, detectedPlatform.getProjectId());
74+
75+
switch (detectedPlatform.getSupportedPlatform()) {
76+
case GOOGLE_KUBERNETES_ENGINE:
77+
addGkeAttributes(attrBuilder, detectedPlatform.getAttributes());
78+
break;
79+
case GOOGLE_CLOUD_RUN:
80+
addGcrAttributes(attrBuilder, detectedPlatform.getAttributes());
81+
break;
82+
case GOOGLE_CLOUD_FUNCTIONS:
83+
addGcfAttributes(attrBuilder, detectedPlatform.getAttributes());
84+
break;
85+
case GOOGLE_APP_ENGINE:
86+
addGaeAttributes(attrBuilder, detectedPlatform.getAttributes());
87+
break;
88+
case GOOGLE_COMPUTE_ENGINE:
89+
addGceAttributes(attrBuilder, detectedPlatform.getAttributes());
90+
break;
91+
default:
92+
// We don't support this platform yet, so just return with what we have
93+
}
94+
95+
return attrBuilder.build();
96+
}
97+
98+
@Override
99+
public Resource createResource(ConfigProperties config) {
100+
return Resource.create(getAttributes());
101+
}
102+
103+
/**
104+
* Updates the attributes builder with required attributes for GCE resource, if GCE resource is
105+
* applicable. By default, if the resource is running on GCP, it is assumed to be GCE. This means
106+
* additional attributes are added/overwritten if later on, the resource is identified to be some
107+
* other platform - like GKE, GAE, etc.
108+
*/
109+
private static void addGceAttributes(
110+
AttributesBuilder attrBuilder, Map<String, String> attributesMap) {
111+
attrBuilder.put(
112+
ResourceAttributes.CLOUD_PLATFORM,
113+
ResourceAttributes.CloudPlatformValues.GCP_COMPUTE_ENGINE);
114+
115+
Optional.ofNullable(attributesMap.get(GCE_AVAILABILITY_ZONE))
116+
.ifPresent(zone -> attrBuilder.put(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, zone));
117+
Optional.ofNullable(attributesMap.get(GCE_CLOUD_REGION))
118+
.ifPresent(region -> attrBuilder.put(ResourceAttributes.CLOUD_REGION, region));
119+
Optional.ofNullable(attributesMap.get(GCE_INSTANCE_ID))
120+
.ifPresent(instanceId -> attrBuilder.put(ResourceAttributes.HOST_ID, instanceId));
121+
Optional.ofNullable(attributesMap.get(GCE_INSTANCE_NAME))
122+
.ifPresent(
123+
instanceName -> {
124+
attrBuilder.put(ResourceAttributes.HOST_NAME, instanceName);
125+
attrBuilder.put(ResourceAttributes.GCP_GCE_INSTANCE_NAME, instanceName);
126+
});
127+
Optional.ofNullable(attributesMap.get(GCE_INSTANCE_HOSTNAME))
128+
.ifPresent(
129+
instanceHostname ->
130+
attrBuilder.put(ResourceAttributes.GCP_GCE_INSTANCE_HOSTNAME, instanceHostname));
131+
Optional.ofNullable(attributesMap.get(GCE_MACHINE_TYPE))
132+
.ifPresent(machineType -> attrBuilder.put(ResourceAttributes.HOST_TYPE, machineType));
133+
}
134+
135+
/**
136+
* Updates the attributes with the required keys for a GKE (Google Kubernetes Engine) environment.
137+
* The attributes are not updated in case the environment is not deemed to be GKE.
138+
*
139+
* @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the
140+
* necessary keys.
141+
*/
142+
private static void addGkeAttributes(
143+
AttributesBuilder attrBuilder, Map<String, String> attributesMap) {
144+
attrBuilder.put(
145+
ResourceAttributes.CLOUD_PLATFORM,
146+
ResourceAttributes.CloudPlatformValues.GCP_KUBERNETES_ENGINE);
147+
148+
Optional.ofNullable(attributesMap.get(GKE_CLUSTER_NAME))
149+
.ifPresent(
150+
clusterName -> attrBuilder.put(ResourceAttributes.K8S_CLUSTER_NAME, clusterName));
151+
Optional.ofNullable(attributesMap.get(GKE_HOST_ID))
152+
.ifPresent(hostId -> attrBuilder.put(ResourceAttributes.HOST_ID, hostId));
153+
Optional.ofNullable(attributesMap.get(GKE_CLUSTER_LOCATION_TYPE))
154+
.ifPresent(
155+
locationType -> {
156+
if (attributesMap.get(GKE_CLUSTER_LOCATION) != null) {
157+
switch (locationType) {
158+
case GKE_LOCATION_TYPE_REGION:
159+
attrBuilder.put(
160+
ResourceAttributes.CLOUD_REGION, attributesMap.get(GKE_CLUSTER_LOCATION));
161+
break;
162+
case GKE_LOCATION_TYPE_ZONE:
163+
attrBuilder.put(
164+
ResourceAttributes.CLOUD_AVAILABILITY_ZONE,
165+
attributesMap.get(GKE_CLUSTER_LOCATION));
166+
break;
167+
default:
168+
// TODO: Figure out how to handle unexpected conditions like this
169+
LOGGER.severe(
170+
String.format(
171+
"Unrecognized format for cluster location: %s",
172+
attributesMap.get(GKE_CLUSTER_LOCATION)));
173+
}
174+
}
175+
});
176+
}
177+
178+
/**
179+
* Updates the attributes with the required keys for a GCR (Google Cloud Run) environment. The
180+
* attributes are not updated in case the environment is not deemed to be GCR.
181+
*
182+
* @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the
183+
* necessary keys.
184+
*/
185+
private static void addGcrAttributes(
186+
AttributesBuilder attrBuilder, Map<String, String> attributesMap) {
187+
attrBuilder.put(
188+
ResourceAttributes.CLOUD_PLATFORM, ResourceAttributes.CloudPlatformValues.GCP_CLOUD_RUN);
189+
addCommonAttributesForServerlessCompute(attrBuilder, attributesMap);
190+
}
191+
192+
/**
193+
* Updates the attributes with the required keys for a GCF (Google Cloud Functions) environment.
194+
* The attributes are not updated in case the environment is not deemed to be GCF.
195+
*
196+
* @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the
197+
* necessary keys.
198+
*/
199+
private static void addGcfAttributes(
200+
AttributesBuilder attrBuilder, Map<String, String> attributesMap) {
201+
attrBuilder.put(
202+
ResourceAttributes.CLOUD_PLATFORM,
203+
ResourceAttributes.CloudPlatformValues.GCP_CLOUD_FUNCTIONS);
204+
addCommonAttributesForServerlessCompute(attrBuilder, attributesMap);
205+
}
206+
207+
/**
208+
* Updates the attributes with the required keys for a GAE (Google App Engine) environment. The
209+
* attributes are not updated in case the environment is not deemed to be GAE.
210+
*
211+
* @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the
212+
* necessary keys.
213+
*/
214+
private static void addGaeAttributes(
215+
AttributesBuilder attrBuilder, Map<String, String> attributesMap) {
216+
attrBuilder.put(
217+
ResourceAttributes.CLOUD_PLATFORM, ResourceAttributes.CloudPlatformValues.GCP_APP_ENGINE);
218+
Optional.ofNullable(attributesMap.get(GAE_MODULE_NAME))
219+
.ifPresent(appName -> attrBuilder.put(ResourceAttributes.FAAS_NAME, appName));
220+
Optional.ofNullable(attributesMap.get(GAE_APP_VERSION))
221+
.ifPresent(appVersion -> attrBuilder.put(ResourceAttributes.FAAS_VERSION, appVersion));
222+
Optional.ofNullable(attributesMap.get(GAE_INSTANCE_ID))
223+
.ifPresent(
224+
appInstanceId -> attrBuilder.put(ResourceAttributes.FAAS_INSTANCE, appInstanceId));
225+
Optional.ofNullable(attributesMap.get(GAE_CLOUD_REGION))
226+
.ifPresent(cloudRegion -> attrBuilder.put(ResourceAttributes.CLOUD_REGION, cloudRegion));
227+
Optional.ofNullable(attributesMap.get(GAE_AVAILABILITY_ZONE))
228+
.ifPresent(
229+
cloudAvailabilityZone ->
230+
attrBuilder.put(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, cloudAvailabilityZone));
231+
}
232+
233+
/**
234+
* This function adds common attributes required for most serverless compute platforms within GCP.
235+
* Currently, these attributes are required for both GCF and GCR.
236+
*
237+
* @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the
238+
* necessary keys.
239+
*/
240+
private static void addCommonAttributesForServerlessCompute(
241+
AttributesBuilder attrBuilder, Map<String, String> attributesMap) {
242+
Optional.ofNullable(attributesMap.get(SERVERLESS_COMPUTE_NAME))
243+
.ifPresent(name -> attrBuilder.put(ResourceAttributes.FAAS_NAME, name));
244+
Optional.ofNullable(attributesMap.get(SERVERLESS_COMPUTE_REVISION))
245+
.ifPresent(revision -> attrBuilder.put(ResourceAttributes.FAAS_VERSION, revision));
246+
Optional.ofNullable(attributesMap.get(SERVERLESS_COMPUTE_INSTANCE_ID))
247+
.ifPresent(instanceId -> attrBuilder.put(ResourceAttributes.FAAS_INSTANCE, instanceId));
248+
Optional.ofNullable(attributesMap.get(SERVERLESS_COMPUTE_AVAILABILITY_ZONE))
249+
.ifPresent(zone -> attrBuilder.put(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, zone));
250+
Optional.ofNullable(attributesMap.get(SERVERLESS_COMPUTE_CLOUD_REGION))
251+
.ifPresent(region -> attrBuilder.put(ResourceAttributes.CLOUD_REGION, region));
252+
}
253+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
io.opentelemetry.contrib.gcp.resource.GCPResourceProvider

0 commit comments

Comments
 (0)