Skip to content

Commit 744e845

Browse files
authored
Unify gcp resource detector (#2747)
1 parent 48dd6b4 commit 744e845

20 files changed

+1411
-81
lines changed

gcp-resources/build.gradle.kts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,6 @@ dependencies {
1212
compileOnly("io.opentelemetry:opentelemetry-api-incubator")
1313
api("io.opentelemetry:opentelemetry-sdk")
1414

15-
// Provides GCP resource detection support
16-
implementation("com.google.cloud.opentelemetry:detector-resources-support:0.36.0")
17-
1815
testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")
1916

2017
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure")
@@ -26,7 +23,9 @@ dependencies {
2623
testImplementation("io.opentelemetry:opentelemetry-sdk-testing")
2724

2825
testImplementation("org.mockito:mockito-core")
26+
testImplementation("org.mockito:mockito-inline")
2927
testImplementation("com.google.guava:guava")
3028

3129
testImplementation("org.junit.jupiter:junit-jupiter-api")
30+
testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.0")
3231
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.gcp.resource;
7+
8+
/**
9+
* Contains constants that act as keys for the known attributes for {@link
10+
* GcpPlatformDetector.SupportedPlatform}s.
11+
*/
12+
final class AttributeKeys {
13+
private AttributeKeys() {}
14+
15+
// GCE Attributes
16+
public static final String GCE_AVAILABILITY_ZONE = AttributeKeys.AVAILABILITY_ZONE;
17+
public static final String GCE_CLOUD_REGION = AttributeKeys.CLOUD_REGION;
18+
public static final String GCE_INSTANCE_ID = AttributeKeys.INSTANCE_ID;
19+
public static final String GCE_INSTANCE_NAME = AttributeKeys.INSTANCE_NAME;
20+
public static final String GCE_MACHINE_TYPE = AttributeKeys.MACHINE_TYPE;
21+
public static final String GCE_INSTANCE_HOSTNAME = "instance_hostname";
22+
23+
// GKE Attributes
24+
public static final String GKE_CLUSTER_NAME = "gke_cluster_name";
25+
public static final String GKE_CLUSTER_LOCATION_TYPE = "gke_cluster_location_type";
26+
public static final String GKE_CLUSTER_LOCATION = "gke_cluster_location";
27+
public static final String GKE_HOST_ID = AttributeKeys.INSTANCE_ID;
28+
29+
// GKE Location Constants
30+
public static final String GKE_LOCATION_TYPE_ZONE = "ZONE";
31+
public static final String GKE_LOCATION_TYPE_REGION = "REGION";
32+
33+
// GAE Attributes
34+
public static final String GAE_MODULE_NAME = "gae_module_name";
35+
public static final String GAE_APP_VERSION = "gae_app_version";
36+
public static final String GAE_INSTANCE_ID = AttributeKeys.INSTANCE_ID;
37+
public static final String GAE_AVAILABILITY_ZONE = AttributeKeys.AVAILABILITY_ZONE;
38+
public static final String GAE_CLOUD_REGION = AttributeKeys.CLOUD_REGION;
39+
40+
// Google Serverless Compute Attributes
41+
public static final String SERVERLESS_COMPUTE_NAME = "serverless_compute_name";
42+
public static final String SERVERLESS_COMPUTE_REVISION = "serverless_compute_revision";
43+
public static final String SERVERLESS_COMPUTE_AVAILABILITY_ZONE = AttributeKeys.AVAILABILITY_ZONE;
44+
public static final String SERVERLESS_COMPUTE_CLOUD_REGION = AttributeKeys.CLOUD_REGION;
45+
public static final String SERVERLESS_COMPUTE_INSTANCE_ID = AttributeKeys.INSTANCE_ID;
46+
47+
// Cloud Run Job Specific Attributes
48+
public static final String GCR_JOB_EXECUTION_KEY = "gcr_job_execution_key";
49+
public static final String GCR_JOB_TASK_INDEX = "gcr_job_task_index";
50+
51+
static final String AVAILABILITY_ZONE = "availability_zone";
52+
static final String CLOUD_REGION = "cloud_region";
53+
static final String INSTANCE_ID = "instance_id";
54+
static final String INSTANCE_NAME = "instance_name";
55+
static final String MACHINE_TYPE = "machine_type";
56+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
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 java.util.Map;
9+
10+
/** Represents a GCP specific platform on which a cloud application can run. */
11+
interface DetectedPlatform {
12+
/**
13+
* Method to retrieve the underlying compute platform on which application is running.
14+
*
15+
* @return the {@link GcpPlatformDetector.SupportedPlatform} representing the Google Cloud
16+
* platform on which application is running.
17+
*/
18+
GcpPlatformDetector.SupportedPlatform getSupportedPlatform();
19+
20+
/**
21+
* Method to retrieve the GCP Project ID in which the GCP specific platform exists. Every valid
22+
* platform must have a GCP Project ID associated with it.
23+
*
24+
* @return the Google Cloud project ID.
25+
*/
26+
String getProjectId();
27+
28+
/**
29+
* Method to retrieve the attributes associated with the compute platform on which the application
30+
* is running as key-value pairs. The valid keys to query on this {@link Map} are specified in the
31+
* {@link AttributeKeys}.
32+
*
33+
* @return a {@link Map} of attributes specific to the underlying compute platform.
34+
*/
35+
Map<String, String> getAttributes();
36+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.contrib.gcp.resource;
7+
8+
/**
9+
* Provides API to fetch environment variables. This is useful in order to create a mock class for
10+
* testing.
11+
*/
12+
interface EnvironmentVariables {
13+
/** Returns the current environment variables of the platform this is running in. */
14+
EnvironmentVariables DEFAULT_INSTANCE = System::getenv;
15+
16+
/**
17+
* Grabs the system environment variable. Returns null on failure.
18+
*
19+
* @param key the key of the environment variable in {@code System.getenv()}
20+
* @return the value received by {@code System.getenv(key)}
21+
*/
22+
String get(String key);
23+
}

gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProvider.java

Lines changed: 28 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -5,30 +5,30 @@
55

66
package io.opentelemetry.contrib.gcp.resource;
77

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.GCR_JOB_EXECUTION_KEY;
20-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCR_JOB_TASK_INDEX;
21-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_CLUSTER_LOCATION;
22-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_CLUSTER_LOCATION_TYPE;
23-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_CLUSTER_NAME;
24-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_HOST_ID;
25-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_LOCATION_TYPE_REGION;
26-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_LOCATION_TYPE_ZONE;
27-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_AVAILABILITY_ZONE;
28-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_CLOUD_REGION;
29-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_INSTANCE_ID;
30-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_NAME;
31-
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_REVISION;
8+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_APP_VERSION;
9+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_AVAILABILITY_ZONE;
10+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_CLOUD_REGION;
11+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_INSTANCE_ID;
12+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_MODULE_NAME;
13+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_AVAILABILITY_ZONE;
14+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_CLOUD_REGION;
15+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_INSTANCE_HOSTNAME;
16+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_INSTANCE_ID;
17+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_INSTANCE_NAME;
18+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_MACHINE_TYPE;
19+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCR_JOB_EXECUTION_KEY;
20+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCR_JOB_TASK_INDEX;
21+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_CLUSTER_LOCATION;
22+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_CLUSTER_LOCATION_TYPE;
23+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_CLUSTER_NAME;
24+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_HOST_ID;
25+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_LOCATION_TYPE_REGION;
26+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_LOCATION_TYPE_ZONE;
27+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_AVAILABILITY_ZONE;
28+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_CLOUD_REGION;
29+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_INSTANCE_ID;
30+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_NAME;
31+
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_REVISION;
3232
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.CLOUD_ACCOUNT_ID;
3333
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.CLOUD_AVAILABILITY_ZONE;
3434
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.CLOUD_PLATFORM;
@@ -50,8 +50,6 @@
5050
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.HOST_TYPE;
5151
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.K8S_CLUSTER_NAME;
5252

53-
import com.google.cloud.opentelemetry.detection.DetectedPlatform;
54-
import com.google.cloud.opentelemetry.detection.GCPPlatformDetector;
5553
import io.opentelemetry.api.common.Attributes;
5654
import io.opentelemetry.api.common.AttributesBuilder;
5755
import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties;
@@ -66,15 +64,15 @@ public class GCPResourceProvider implements ConditionalResourceProvider {
6664

6765
private static final Logger LOGGER = Logger.getLogger(GCPResourceProvider.class.getSimpleName());
6866

69-
private final GCPPlatformDetector detector;
67+
private final GcpPlatformDetector detector;
7068

7169
// for testing only
72-
GCPResourceProvider(GCPPlatformDetector detector) {
70+
GCPResourceProvider(GcpPlatformDetector detector) {
7371
this.detector = detector;
7472
}
7573

7674
public GCPResourceProvider() {
77-
this.detector = GCPPlatformDetector.DEFAULT_INSTANCE;
75+
this.detector = GcpPlatformDetector.DEFAULT_INSTANCE;
7876
}
7977

8078
@Override
@@ -91,7 +89,7 @@ public final boolean shouldApply(ConfigProperties config, Resource existing) {
9189
public Attributes getAttributes() {
9290
DetectedPlatform detectedPlatform = detector.detectPlatform();
9391
if (detectedPlatform.getSupportedPlatform()
94-
== GCPPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM) {
92+
== GcpPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM) {
9593
return Attributes.empty();
9694
}
9795

Lines changed: 163 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,163 @@
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 java.io.BufferedReader;
9+
import java.io.IOException;
10+
import java.io.InputStream;
11+
import java.io.InputStreamReader;
12+
import java.net.HttpURLConnection;
13+
import java.net.URI;
14+
import java.net.URL;
15+
import java.nio.charset.StandardCharsets;
16+
import java.util.Map;
17+
import java.util.concurrent.ConcurrentHashMap;
18+
import javax.annotation.Nullable;
19+
20+
/**
21+
* Retrieves Google Cloud project-id and a limited set of instance attributes from Metadata server.
22+
*
23+
* @see <a href= "https://cloud.google.com/compute/docs/storing-retrieving-metadata">
24+
* https://cloud.google.com/compute/docs/storing-retrieving-metadata</a>
25+
*/
26+
final class GcpMetadataConfig {
27+
static final GcpMetadataConfig DEFAULT_INSTANCE = new GcpMetadataConfig();
28+
29+
private static final String DEFAULT_URL = "http://metadata.google.internal/computeMetadata/v1/";
30+
private final String url;
31+
private final Map<String, String> cachedAttributes = new ConcurrentHashMap<>();
32+
33+
private GcpMetadataConfig() {
34+
this.url = DEFAULT_URL;
35+
}
36+
37+
// For testing only
38+
GcpMetadataConfig(String url) {
39+
this.url = url;
40+
}
41+
42+
// Returns null on failure to retrieve from metadata server
43+
String getProjectId() {
44+
return getAttribute("project/project-id");
45+
}
46+
47+
/**
48+
* Method to extract cloud availability zone from the metadata server.
49+
*
50+
* <p>Example response: projects/640212054955/zones/australia-southeast1-a
51+
*
52+
* <p>Example zone: australia-southeast1-a
53+
*
54+
* @return the extracted zone from the metadata server response or null in case of failure to
55+
* retrieve from metadata server.
56+
*/
57+
String getZone() {
58+
String zone = getAttribute("instance/zone");
59+
if (zone != null && zone.contains("/")) {
60+
zone = zone.substring(zone.lastIndexOf('/') + 1);
61+
}
62+
return zone;
63+
}
64+
65+
/**
66+
* Use this method only when the region cannot be parsed from the zone. Known use-cases of this
67+
* method involve detecting region in GAE standard environment.
68+
*
69+
* <p>Example response: projects/5689182099321/regions/us-central1.
70+
*
71+
* @return the retrieved region or null in case of failure to retrieve from metadata server
72+
*/
73+
String getRegion() {
74+
String region = getAttribute("instance/region");
75+
if (region != null && region.contains("/")) {
76+
region = region.substring(region.lastIndexOf('/') + 1);
77+
}
78+
return region;
79+
}
80+
81+
/**
82+
* Use this method to parse region from zone.
83+
*
84+
* <p>Example region: australia-southeast1
85+
*
86+
* @return parsed region from the zone, if zone is not found or is invalid, this method returns
87+
* null.
88+
*/
89+
@Nullable
90+
String getRegionFromZone() {
91+
String region = null;
92+
String zone = getZone();
93+
if (zone != null && !zone.isEmpty()) {
94+
// Parsing required to scope up to a region
95+
String[] splitArr = zone.split("-");
96+
if (splitArr.length > 2) {
97+
region = String.join("-", splitArr[0], splitArr[1]);
98+
}
99+
}
100+
return region;
101+
}
102+
103+
// Example response: projects/640212054955/machineTypes/e2-medium
104+
String getMachineType() {
105+
String machineType = getAttribute("instance/machine-type");
106+
if (machineType != null && machineType.contains("/")) {
107+
machineType = machineType.substring(machineType.lastIndexOf('/') + 1);
108+
}
109+
return machineType;
110+
}
111+
112+
// Returns null on failure to retrieve from metadata server
113+
String getInstanceId() {
114+
return getAttribute("instance/id");
115+
}
116+
117+
// Returns null on failure to retrieve from metadata server
118+
String getClusterName() {
119+
return getAttribute("instance/attributes/cluster-name");
120+
}
121+
122+
// Returns null on failure to retrieve from metadata server
123+
String getClusterLocation() {
124+
return getAttribute("instance/attributes/cluster-location");
125+
}
126+
127+
// Returns null on failure to retrieve from metadata server
128+
String getInstanceHostName() {
129+
return getAttribute("instance/hostname");
130+
}
131+
132+
// Returns null on failure to retrieve from metadata server
133+
String getInstanceName() {
134+
return getAttribute("instance/name");
135+
}
136+
137+
// Returns null on failure to retrieve from metadata server
138+
private String getAttribute(String attributeName) {
139+
return cachedAttributes.computeIfAbsent(attributeName, this::fetchAttribute);
140+
}
141+
142+
// Return the attribute received at <attributeName> relative path or null on
143+
// failure
144+
@Nullable
145+
private String fetchAttribute(String attributeName) {
146+
try {
147+
URL url = URI.create(this.url + attributeName).toURL();
148+
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
149+
connection.setRequestProperty("Metadata-Flavor", "Google");
150+
if (connection.getResponseCode() == 200
151+
&& "Google".equals(connection.getHeaderField("Metadata-Flavor"))) {
152+
InputStream input = connection.getInputStream();
153+
try (BufferedReader reader =
154+
new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
155+
return reader.readLine();
156+
}
157+
}
158+
} catch (IOException ignore) {
159+
// ignore
160+
}
161+
return null;
162+
}
163+
}

0 commit comments

Comments
 (0)