Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 2 additions & 3 deletions gcp-resources/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ dependencies {
compileOnly("io.opentelemetry:opentelemetry-api-incubator")
api("io.opentelemetry:opentelemetry-sdk")

// Provides GCP resource detection support
implementation("com.google.cloud.opentelemetry:detector-resources-support:0.36.0")

testImplementation("io.opentelemetry.semconv:opentelemetry-semconv-incubating")

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

testImplementation("org.mockito:mockito-core")
testImplementation("org.mockito:mockito-inline")
testImplementation("com.google.guava:guava")

testImplementation("org.junit.jupiter:junit-jupiter-api")
testImplementation("com.github.tomakehurst:wiremock-jre8:2.35.0")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.gcp.resource;

/**
* Contains constants that act as keys for the known attributes for {@link
* GcpPlatformDetector.SupportedPlatform}s.
*/
final class AttributeKeys {
private 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;

// Cloud Run Job Specific Attributes
public static final String GCR_JOB_EXECUTION_KEY = "gcr_job_execution_key";
public static final String GCR_JOB_TASK_INDEX = "gcr_job_task_index";

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,36 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.gcp.resource;

import java.util.Map;

/** Represents a GCP specific platform on which a cloud application can run. */
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,23 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.gcp.resource;

/**
* 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
Expand Up @@ -5,30 +5,30 @@

package io.opentelemetry.contrib.gcp.resource;

import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_APP_VERSION;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_AVAILABILITY_ZONE;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_CLOUD_REGION;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_INSTANCE_ID;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GAE_MODULE_NAME;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_AVAILABILITY_ZONE;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_CLOUD_REGION;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_INSTANCE_HOSTNAME;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_INSTANCE_ID;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_INSTANCE_NAME;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCE_MACHINE_TYPE;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCR_JOB_EXECUTION_KEY;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GCR_JOB_TASK_INDEX;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_CLUSTER_LOCATION;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_CLUSTER_LOCATION_TYPE;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_CLUSTER_NAME;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_HOST_ID;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_LOCATION_TYPE_REGION;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.GKE_LOCATION_TYPE_ZONE;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_AVAILABILITY_ZONE;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_CLOUD_REGION;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_INSTANCE_ID;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_NAME;
import static com.google.cloud.opentelemetry.detection.AttributeKeys.SERVERLESS_COMPUTE_REVISION;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_APP_VERSION;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_AVAILABILITY_ZONE;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_CLOUD_REGION;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_INSTANCE_ID;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GAE_MODULE_NAME;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_AVAILABILITY_ZONE;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_CLOUD_REGION;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_INSTANCE_HOSTNAME;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_INSTANCE_ID;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_INSTANCE_NAME;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCE_MACHINE_TYPE;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCR_JOB_EXECUTION_KEY;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GCR_JOB_TASK_INDEX;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_CLUSTER_LOCATION;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_CLUSTER_LOCATION_TYPE;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_CLUSTER_NAME;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_HOST_ID;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_LOCATION_TYPE_REGION;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.GKE_LOCATION_TYPE_ZONE;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_AVAILABILITY_ZONE;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_CLOUD_REGION;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_INSTANCE_ID;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_NAME;
import static io.opentelemetry.contrib.gcp.resource.AttributeKeys.SERVERLESS_COMPUTE_REVISION;
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.CLOUD_ACCOUNT_ID;
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.CLOUD_AVAILABILITY_ZONE;
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.CLOUD_PLATFORM;
Expand All @@ -50,8 +50,6 @@
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.HOST_TYPE;
import static io.opentelemetry.contrib.gcp.resource.IncubatingAttributes.K8S_CLUSTER_NAME;

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

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

private final GCPPlatformDetector detector;
private final GcpPlatformDetector detector;

// for testing only
GCPResourceProvider(GCPPlatformDetector detector) {
GCPResourceProvider(GcpPlatformDetector detector) {
this.detector = detector;
}

public GCPResourceProvider() {
this.detector = GCPPlatformDetector.DEFAULT_INSTANCE;
this.detector = GcpPlatformDetector.DEFAULT_INSTANCE;
}

@Override
Expand All @@ -91,7 +89,7 @@ public final boolean shouldApply(ConfigProperties config, Resource existing) {
public Attributes getAttributes() {
DetectedPlatform detectedPlatform = detector.detectPlatform();
if (detectedPlatform.getSupportedPlatform()
== GCPPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM) {
== GcpPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM) {
return Attributes.empty();
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,163 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

package io.opentelemetry.contrib.gcp.resource;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URI;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import javax.annotation.Nullable;

/**
* 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 ConcurrentHashMap<>();

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.
*/
@Nullable
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
@Nullable
private String fetchAttribute(String attributeName) {
try {
URL url = URI.create(this.url + attributeName).toURL();
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
Loading