Skip to content

Commit 60dfec0

Browse files
authored
Add new module for resource-detector support library (#276)
* Add new module for resource-detector support library * Rname GCPResource -> GCPResourceProvider * Extract GCPMetadataConfig into support library * Refactor platform detection from resource provider * Refactor platform detection to enhance testing * make EnvironmentVariables package-private * Add license headers to the newly added files * Fix spotless style findings * Remove CloudLocationUtil class * change attribute map to hold string instead of optional values * add tests to verify resource attributes mapping * add javadoc to public classes * add unit test for ServiceLoader discoverability * Remove no-args constructor for simplified testing * Update gradle files to support junit5 testing * Add missing attribute in GKE mapping * Add tests for resources-support library * Update copyright year in GoogleCloudRun.java * Add missing newline * Update resource-detector tests to JUnit5 * Remove non-GCP attributes from GKE detection * Add missing attributes in detected GCE environment * Update tests for GAE * Add CLOUD_ACCOUNT_ID to all supported platforms * Rename support package: detectors -> detection Support library was using the same package as the detectors library. This rename would avoid potential conflicts and import issues. * Undo breaking changes These breaking chnages can be directly introduced when we contribute the resource detector upstream. This commit adds classes back and marks them deprecated to indicate to the users not to depend on them.
1 parent de1d4b4 commit 60dfec0

26 files changed

Lines changed: 1851 additions & 410 deletions

File tree

build.gradle

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -147,10 +147,11 @@ subprojects {
147147
openTelemetryInstrumentationVersion = '1.31.0'
148148
openTelemetrySemconvVersion = '1.22.0'
149149
junitVersion = '4.13'
150+
junit5Version = '5.10.0'
150151
mockitoVersion = '3.5.10'
151152
pubSubVersion = '1.125.11'
152153
testContainersVersion = '1.15.1'
153-
wiremockVersion = '2.27.2'
154+
wiremockVersion = '2.35.0'
154155
springWebVersion = '2.4.5'
155156
springOpenFeignVersion = '3.0.0'
156157
springOtelVersion = '1.0.0-M8'
@@ -194,6 +195,9 @@ subprojects {
194195
testLibraries = [
195196
assertj : "org.assertj:assertj-core:${assertjVersion}",
196197
junit : "junit:junit:${junitVersion}",
198+
junit5 : "org.junit.jupiter:junit-jupiter-api:${junit5Version}",
199+
junit5_runtime : "org.junit.jupiter:junit-jupiter-engine:${junit5Version}",
200+
junit5_params : "org.junit.jupiter:junit-jupiter-params:${junit5Version}",
197201
mockito : "org.mockito:mockito-inline:${mockitoVersion}",
198202
slf4j_simple: "org.slf4j:slf4j-simple:${slf4jVersion}",
199203
opentelemetry_sdk_testing: "io.opentelemetry:opentelemetry-sdk-testing:${openTelemetryVersion}",
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
description = 'Support library for Google Cloud Resource Detector'
17+
18+
dependencies {
19+
testImplementation(testLibraries.assertj)
20+
testImplementation(testLibraries.wiremock)
21+
testImplementation(testLibraries.mockito)
22+
testImplementation(testLibraries.junit5)
23+
testImplementation(testLibraries.junit5_params)
24+
testRuntimeOnly(testLibraries.junit5_runtime)
25+
}
26+
27+
afterEvaluate {
28+
tasks.named("compileJava"){
29+
options.release = 8
30+
}
31+
}
32+
33+
test {
34+
// required for discovering JUnit 5 tests
35+
useJUnitPlatform()
36+
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.opentelemetry.detection;
17+
18+
/**
19+
* Contains constants that act as keys for the known attributes for {@link
20+
* GCPPlatformDetector.SupportedPlatform}s.
21+
*/
22+
public final class AttributeKeys {
23+
// GCE Attributes
24+
public static final String GCE_AVAILABILITY_ZONE = AttributeKeys.AVAILABILITY_ZONE;
25+
public static final String GCE_CLOUD_REGION = AttributeKeys.CLOUD_REGION;
26+
public static final String GCE_INSTANCE_ID = AttributeKeys.INSTANCE_ID;
27+
public static final String GCE_INSTANCE_NAME = AttributeKeys.INSTANCE_NAME;
28+
public static final String GCE_MACHINE_TYPE = AttributeKeys.MACHINE_TYPE;
29+
public static final String GCE_INSTANCE_HOSTNAME = "instance_hostname";
30+
31+
// GKE Attributes
32+
public static final String GKE_CLUSTER_NAME = "gke_cluster_name";
33+
public static final String GKE_CLUSTER_LOCATION_TYPE = "gke_cluster_location_type";
34+
public static final String GKE_CLUSTER_LOCATION = "gke_cluster_location";
35+
public static final String GKE_HOST_ID = AttributeKeys.INSTANCE_ID;
36+
37+
// GKE Location Constants
38+
public static final String GKE_LOCATION_TYPE_ZONE = "ZONE";
39+
public static final String GKE_LOCATION_TYPE_REGION = "REGION";
40+
41+
// GAE Attributes
42+
public static final String GAE_MODULE_NAME = "gae_module_name";
43+
public static final String GAE_APP_VERSION = "gae_app_version";
44+
public static final String GAE_INSTANCE_ID = AttributeKeys.INSTANCE_ID;
45+
public static final String GAE_AVAILABILITY_ZONE = AttributeKeys.AVAILABILITY_ZONE;
46+
public static final String GAE_CLOUD_REGION = AttributeKeys.CLOUD_REGION;
47+
48+
// Google Serverless Compute Attributes
49+
public static final String SERVERLESS_COMPUTE_NAME = "serverless_compute_name";
50+
public static final String SERVERLESS_COMPUTE_REVISION = "serverless_compute_revision";
51+
public static final String SERVERLESS_COMPUTE_AVAILABILITY_ZONE = AttributeKeys.AVAILABILITY_ZONE;
52+
public static final String SERVERLESS_COMPUTE_CLOUD_REGION = AttributeKeys.CLOUD_REGION;
53+
public static final String SERVERLESS_COMPUTE_INSTANCE_ID = AttributeKeys.INSTANCE_ID;
54+
55+
static final String AVAILABILITY_ZONE = "availability_zone";
56+
static final String CLOUD_REGION = "cloud_region";
57+
static final String INSTANCE_ID = "instance_id";
58+
static final String INSTANCE_NAME = "instance_name";
59+
static final String MACHINE_TYPE = "machine_type";
60+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.opentelemetry.detection;
17+
18+
import java.util.Map;
19+
20+
/** Represents a GCP specific platform on which a cloud application can run. */
21+
public interface DetectedPlatform {
22+
/**
23+
* Method to retrieve the underlying compute platform on which application is running.
24+
*
25+
* @return the {@link GCPPlatformDetector.SupportedPlatform} representing the Google Cloud
26+
* platform on which application is running.
27+
*/
28+
GCPPlatformDetector.SupportedPlatform getSupportedPlatform();
29+
30+
/**
31+
* Method to retrieve the GCP Project ID in which the GCP specific platform exists. Every valid
32+
* platform must have a GCP Project ID associated with it.
33+
*
34+
* @return the Google Cloud project ID.
35+
*/
36+
String getProjectId();
37+
38+
/**
39+
* Method to retrieve the attributes associated with the compute platform on which the application
40+
* is running as key-value pairs. The valid keys to query on this {@link Map} are specified in the
41+
* {@link AttributeKeys}.
42+
*
43+
* @return a {@link Map} of attributes specific to the underlying compute platform.
44+
*/
45+
Map<String, String> getAttributes();
46+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.opentelemetry.detection;
17+
18+
/**
19+
* Provides API to fetch environment variables. This is useful in order to create a mock class for
20+
* testing.
21+
*/
22+
interface EnvironmentVariables {
23+
/** Returns the current environment variables of the platform this is running in. */
24+
EnvironmentVariables DEFAULT_INSTANCE = System::getenv;
25+
26+
/**
27+
* Grabs the system environment variable. Returns null on failure.
28+
*
29+
* @param key the key of the environment variable in {@code System.getenv()}
30+
* @return the value received by {@code System.getenv(key)}
31+
*/
32+
String get(String key);
33+
}
Lines changed: 168 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
/*
2+
* Copyright 2024 Google LLC
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package com.google.cloud.opentelemetry.detection;
17+
18+
import java.io.BufferedReader;
19+
import java.io.IOException;
20+
import java.io.InputStream;
21+
import java.io.InputStreamReader;
22+
import java.net.HttpURLConnection;
23+
import java.net.URL;
24+
import java.nio.charset.StandardCharsets;
25+
import java.util.HashMap;
26+
import java.util.Map;
27+
28+
/**
29+
* Retrieves Google Cloud project-id and a limited set of instance attributes from Metadata server.
30+
*
31+
* @see <a href="https://cloud.google.com/compute/docs/storing-retrieving-metadata">
32+
* https://cloud.google.com/compute/docs/storing-retrieving-metadata</a>
33+
*/
34+
final class GCPMetadataConfig {
35+
static final GCPMetadataConfig DEFAULT_INSTANCE = new GCPMetadataConfig();
36+
37+
private static final String DEFAULT_URL = "http://metadata.google.internal/computeMetadata/v1/";
38+
private final String url;
39+
private final Map<String, String> cachedAttributes = new HashMap<>();
40+
41+
private GCPMetadataConfig() {
42+
this.url = DEFAULT_URL;
43+
}
44+
45+
// For testing only
46+
GCPMetadataConfig(String url) {
47+
this.url = url;
48+
}
49+
50+
// Returns null on failure to retrieve from metadata server
51+
String getProjectId() {
52+
return getAttribute("project/project-id");
53+
}
54+
55+
/**
56+
* Method to extract cloud availability zone from the metadata server.
57+
*
58+
* <p>Example response: projects/640212054955/zones/australia-southeast1-a
59+
*
60+
* <p>Example zone: australia-southeast1-a
61+
*
62+
* @return the extracted zone from the metadata server response or null in case of failure to
63+
* retrieve from metadata server.
64+
*/
65+
String getZone() {
66+
String zone = getAttribute("instance/zone");
67+
if (zone != null && zone.contains("/")) {
68+
zone = zone.substring(zone.lastIndexOf('/') + 1);
69+
}
70+
return zone;
71+
}
72+
73+
/**
74+
* Use this method only when the region cannot be parsed from the zone. Known use-cases of this
75+
* method involve detecting region in GAE standard environment.
76+
*
77+
* <p>Example response: projects/5689182099321/regions/us-central1.
78+
*
79+
* @return the retrieved region or null in case of failure to retrieve from metadata server
80+
*/
81+
String getRegion() {
82+
String region = getAttribute("instance/region");
83+
if (region != null && region.contains("/")) {
84+
region = region.substring(region.lastIndexOf('/') + 1);
85+
}
86+
return region;
87+
}
88+
89+
/**
90+
* Use this method to parse region from zone.
91+
*
92+
* <p>Example region: australia-southeast1
93+
*
94+
* @return parsed region from the zone, if zone is not found or is invalid, this method returns
95+
* null.
96+
*/
97+
String getRegionFromZone() {
98+
String region = null;
99+
String zone = getZone();
100+
if (zone != null && !zone.isEmpty()) {
101+
// Parsing required to scope up to a region
102+
String[] splitArr = zone.split("-");
103+
if (splitArr.length > 2) {
104+
region = String.join("-", splitArr[0], splitArr[1]);
105+
}
106+
}
107+
return region;
108+
}
109+
110+
// Example response: projects/640212054955/machineTypes/e2-medium
111+
String getMachineType() {
112+
String machineType = getAttribute("instance/machine-type");
113+
if (machineType != null && machineType.contains("/")) {
114+
machineType = machineType.substring(machineType.lastIndexOf('/') + 1);
115+
}
116+
return machineType;
117+
}
118+
119+
// Returns null on failure to retrieve from metadata server
120+
String getInstanceId() {
121+
return getAttribute("instance/id");
122+
}
123+
124+
// Returns null on failure to retrieve from metadata server
125+
String getClusterName() {
126+
return getAttribute("instance/attributes/cluster-name");
127+
}
128+
129+
// Returns null on failure to retrieve from metadata server
130+
String getClusterLocation() {
131+
return getAttribute("instance/attributes/cluster-location");
132+
}
133+
134+
// Returns null on failure to retrieve from metadata server
135+
String getInstanceHostName() {
136+
return getAttribute("instance/hostname");
137+
}
138+
139+
// Returns null on failure to retrieve from metadata server
140+
String getInstanceName() {
141+
return getAttribute("instance/name");
142+
}
143+
144+
// Returns null on failure to retrieve from metadata server
145+
private String getAttribute(String attributeName) {
146+
return cachedAttributes.computeIfAbsent(attributeName, this::fetchAttribute);
147+
}
148+
149+
// Return the attribute received at <attributeName> relative path or null on failure
150+
private String fetchAttribute(String attributeName) {
151+
try {
152+
URL url = new URL(this.url + attributeName);
153+
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
154+
connection.setRequestProperty("Metadata-Flavor", "Google");
155+
if (connection.getResponseCode() == 200
156+
&& ("Google").equals(connection.getHeaderField("Metadata-Flavor"))) {
157+
InputStream input = connection.getInputStream();
158+
try (BufferedReader reader =
159+
new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
160+
return reader.readLine();
161+
}
162+
}
163+
} catch (IOException ignore) {
164+
// ignore
165+
}
166+
return null;
167+
}
168+
}

0 commit comments

Comments
 (0)