Skip to content

Commit

Permalink
Add new module for resource-detector support library (#276)
Browse files Browse the repository at this point in the history
* 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.
  • Loading branch information
psx95 authored Jan 16, 2024
1 parent de1d4b4 commit 60dfec0
Show file tree
Hide file tree
Showing 26 changed files with 1,851 additions and 410 deletions.
6 changes: 5 additions & 1 deletion build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -147,10 +147,11 @@ subprojects {
openTelemetryInstrumentationVersion = '1.31.0'
openTelemetrySemconvVersion = '1.22.0'
junitVersion = '4.13'
junit5Version = '5.10.0'
mockitoVersion = '3.5.10'
pubSubVersion = '1.125.11'
testContainersVersion = '1.15.1'
wiremockVersion = '2.27.2'
wiremockVersion = '2.35.0'
springWebVersion = '2.4.5'
springOpenFeignVersion = '3.0.0'
springOtelVersion = '1.0.0-M8'
Expand Down Expand Up @@ -194,6 +195,9 @@ subprojects {
testLibraries = [
assertj : "org.assertj:assertj-core:${assertjVersion}",
junit : "junit:junit:${junitVersion}",
junit5 : "org.junit.jupiter:junit-jupiter-api:${junit5Version}",
junit5_runtime : "org.junit.jupiter:junit-jupiter-engine:${junit5Version}",
junit5_params : "org.junit.jupiter:junit-jupiter-params:${junit5Version}",
mockito : "org.mockito:mockito-inline:${mockitoVersion}",
slf4j_simple: "org.slf4j:slf4j-simple:${slf4jVersion}",
opentelemetry_sdk_testing: "io.opentelemetry:opentelemetry-sdk-testing:${openTelemetryVersion}",
Expand Down
36 changes: 36 additions & 0 deletions detectors/resources-support/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
description = 'Support library for Google Cloud Resource Detector'

dependencies {
testImplementation(testLibraries.assertj)
testImplementation(testLibraries.wiremock)
testImplementation(testLibraries.mockito)
testImplementation(testLibraries.junit5)
testImplementation(testLibraries.junit5_params)
testRuntimeOnly(testLibraries.junit5_runtime)
}

afterEvaluate {
tasks.named("compileJava"){
options.release = 8
}
}

test {
// required for discovering JUnit 5 tests
useJUnitPlatform()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.opentelemetry.detection;

/**
* Contains constants that act as keys for the known attributes for {@link
* GCPPlatformDetector.SupportedPlatform}s.
*/
public final class AttributeKeys {
// GCE Attributes
public static final String GCE_AVAILABILITY_ZONE = AttributeKeys.AVAILABILITY_ZONE;
public static final String GCE_CLOUD_REGION = AttributeKeys.CLOUD_REGION;
public static final String GCE_INSTANCE_ID = AttributeKeys.INSTANCE_ID;
public static final String GCE_INSTANCE_NAME = AttributeKeys.INSTANCE_NAME;
public static final String GCE_MACHINE_TYPE = AttributeKeys.MACHINE_TYPE;
public static final String GCE_INSTANCE_HOSTNAME = "instance_hostname";

// GKE Attributes
public static final String GKE_CLUSTER_NAME = "gke_cluster_name";
public static final String GKE_CLUSTER_LOCATION_TYPE = "gke_cluster_location_type";
public static final String GKE_CLUSTER_LOCATION = "gke_cluster_location";
public static final String GKE_HOST_ID = AttributeKeys.INSTANCE_ID;

// GKE Location Constants
public static final String GKE_LOCATION_TYPE_ZONE = "ZONE";
public static final String GKE_LOCATION_TYPE_REGION = "REGION";

// GAE Attributes
public static final String GAE_MODULE_NAME = "gae_module_name";
public static final String GAE_APP_VERSION = "gae_app_version";
public static final String GAE_INSTANCE_ID = AttributeKeys.INSTANCE_ID;
public static final String GAE_AVAILABILITY_ZONE = AttributeKeys.AVAILABILITY_ZONE;
public static final String GAE_CLOUD_REGION = AttributeKeys.CLOUD_REGION;

// Google Serverless Compute Attributes
public static final String SERVERLESS_COMPUTE_NAME = "serverless_compute_name";
public static final String SERVERLESS_COMPUTE_REVISION = "serverless_compute_revision";
public static final String SERVERLESS_COMPUTE_AVAILABILITY_ZONE = AttributeKeys.AVAILABILITY_ZONE;
public static final String SERVERLESS_COMPUTE_CLOUD_REGION = AttributeKeys.CLOUD_REGION;
public static final String SERVERLESS_COMPUTE_INSTANCE_ID = AttributeKeys.INSTANCE_ID;

static final String AVAILABILITY_ZONE = "availability_zone";
static final String CLOUD_REGION = "cloud_region";
static final String INSTANCE_ID = "instance_id";
static final String INSTANCE_NAME = "instance_name";
static final String MACHINE_TYPE = "machine_type";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.opentelemetry.detection;

import java.util.Map;

/** Represents a GCP specific platform on which a cloud application can run. */
public interface DetectedPlatform {
/**
* Method to retrieve the underlying compute platform on which application is running.
*
* @return the {@link GCPPlatformDetector.SupportedPlatform} representing the Google Cloud
* platform on which application is running.
*/
GCPPlatformDetector.SupportedPlatform getSupportedPlatform();

/**
* Method to retrieve the GCP Project ID in which the GCP specific platform exists. Every valid
* platform must have a GCP Project ID associated with it.
*
* @return the Google Cloud project ID.
*/
String getProjectId();

/**
* Method to retrieve the attributes associated with the compute platform on which the application
* is running as key-value pairs. The valid keys to query on this {@link Map} are specified in the
* {@link AttributeKeys}.
*
* @return a {@link Map} of attributes specific to the underlying compute platform.
*/
Map<String, String> getAttributes();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.opentelemetry.detection;

/**
* Provides API to fetch environment variables. This is useful in order to create a mock class for
* testing.
*/
interface EnvironmentVariables {
/** Returns the current environment variables of the platform this is running in. */
EnvironmentVariables DEFAULT_INSTANCE = System::getenv;

/**
* Grabs the system environment variable. Returns null on failure.
*
* @param key the key of the environment variable in {@code System.getenv()}
* @return the value received by {@code System.getenv(key)}
*/
String get(String key);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,168 @@
/*
* Copyright 2024 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.cloud.opentelemetry.detection;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.Map;

/**
* Retrieves Google Cloud project-id and a limited set of instance attributes from Metadata server.
*
* @see <a href="https://cloud.google.com/compute/docs/storing-retrieving-metadata">
* https://cloud.google.com/compute/docs/storing-retrieving-metadata</a>
*/
final class GCPMetadataConfig {
static final GCPMetadataConfig DEFAULT_INSTANCE = new GCPMetadataConfig();

private static final String DEFAULT_URL = "http://metadata.google.internal/computeMetadata/v1/";
private final String url;
private final Map<String, String> cachedAttributes = new HashMap<>();

private GCPMetadataConfig() {
this.url = DEFAULT_URL;
}

// For testing only
GCPMetadataConfig(String url) {
this.url = url;
}

// Returns null on failure to retrieve from metadata server
String getProjectId() {
return getAttribute("project/project-id");
}

/**
* Method to extract cloud availability zone from the metadata server.
*
* <p>Example response: projects/640212054955/zones/australia-southeast1-a
*
* <p>Example zone: australia-southeast1-a
*
* @return the extracted zone from the metadata server response or null in case of failure to
* retrieve from metadata server.
*/
String getZone() {
String zone = getAttribute("instance/zone");
if (zone != null && zone.contains("/")) {
zone = zone.substring(zone.lastIndexOf('/') + 1);
}
return zone;
}

/**
* Use this method only when the region cannot be parsed from the zone. Known use-cases of this
* method involve detecting region in GAE standard environment.
*
* <p>Example response: projects/5689182099321/regions/us-central1.
*
* @return the retrieved region or null in case of failure to retrieve from metadata server
*/
String getRegion() {
String region = getAttribute("instance/region");
if (region != null && region.contains("/")) {
region = region.substring(region.lastIndexOf('/') + 1);
}
return region;
}

/**
* Use this method to parse region from zone.
*
* <p>Example region: australia-southeast1
*
* @return parsed region from the zone, if zone is not found or is invalid, this method returns
* null.
*/
String getRegionFromZone() {
String region = null;
String zone = getZone();
if (zone != null && !zone.isEmpty()) {
// Parsing required to scope up to a region
String[] splitArr = zone.split("-");
if (splitArr.length > 2) {
region = String.join("-", splitArr[0], splitArr[1]);
}
}
return region;
}

// Example response: projects/640212054955/machineTypes/e2-medium
String getMachineType() {
String machineType = getAttribute("instance/machine-type");
if (machineType != null && machineType.contains("/")) {
machineType = machineType.substring(machineType.lastIndexOf('/') + 1);
}
return machineType;
}

// Returns null on failure to retrieve from metadata server
String getInstanceId() {
return getAttribute("instance/id");
}

// Returns null on failure to retrieve from metadata server
String getClusterName() {
return getAttribute("instance/attributes/cluster-name");
}

// Returns null on failure to retrieve from metadata server
String getClusterLocation() {
return getAttribute("instance/attributes/cluster-location");
}

// Returns null on failure to retrieve from metadata server
String getInstanceHostName() {
return getAttribute("instance/hostname");
}

// Returns null on failure to retrieve from metadata server
String getInstanceName() {
return getAttribute("instance/name");
}

// Returns null on failure to retrieve from metadata server
private String getAttribute(String attributeName) {
return cachedAttributes.computeIfAbsent(attributeName, this::fetchAttribute);
}

// Return the attribute received at <attributeName> relative path or null on failure
private String fetchAttribute(String attributeName) {
try {
URL url = new URL(this.url + attributeName);
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
connection.setRequestProperty("Metadata-Flavor", "Google");
if (connection.getResponseCode() == 200
&& ("Google").equals(connection.getHeaderField("Metadata-Flavor"))) {
InputStream input = connection.getInputStream();
try (BufferedReader reader =
new BufferedReader(new InputStreamReader(input, StandardCharsets.UTF_8))) {
return reader.readLine();
}
}
} catch (IOException ignore) {
// ignore
}
return null;
}
}
Loading

0 comments on commit 60dfec0

Please sign in to comment.