diff --git a/gcp-resources/build.gradle.kts b/gcp-resources/build.gradle.kts index cc227ed6e..47a5695de 100644 --- a/gcp-resources/build.gradle.kts +++ b/gcp-resources/build.gradle.kts @@ -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") @@ -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") } diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/AttributeKeys.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/AttributeKeys.java new file mode 100644 index 000000000..55169332c --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/AttributeKeys.java @@ -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"; +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/DetectedPlatform.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/DetectedPlatform.java new file mode 100644 index 000000000..63120ba1b --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/DetectedPlatform.java @@ -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 getAttributes(); +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/EnvironmentVariables.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/EnvironmentVariables.java new file mode 100644 index 000000000..14711ac52 --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/EnvironmentVariables.java @@ -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); +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProvider.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProvider.java index 4b92ade57..8d1ffce71 100644 --- a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProvider.java +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProvider.java @@ -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; @@ -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; @@ -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 @@ -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(); } diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GcpMetadataConfig.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GcpMetadataConfig.java new file mode 100644 index 000000000..a61c781c1 --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GcpMetadataConfig.java @@ -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 + * https://cloud.google.com/compute/docs/storing-retrieving-metadata + */ +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 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. + * + *

Example response: projects/640212054955/zones/australia-southeast1-a + * + *

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. + * + *

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. + * + *

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 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; + } +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GcpPlatformDetector.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GcpPlatformDetector.java new file mode 100644 index 000000000..c668e8e12 --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GcpPlatformDetector.java @@ -0,0 +1,105 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +class GcpPlatformDetector { + public static final GcpPlatformDetector DEFAULT_INSTANCE = new GcpPlatformDetector(); + + private final GcpMetadataConfig metadataConfig; + private final EnvironmentVariables environmentVariables; + + // for testing only + GcpPlatformDetector(GcpMetadataConfig metadataConfig, EnvironmentVariables environmentVariables) { + this.metadataConfig = metadataConfig; + this.environmentVariables = environmentVariables; + } + + private GcpPlatformDetector() { + this.metadataConfig = GcpMetadataConfig.DEFAULT_INSTANCE; + this.environmentVariables = EnvironmentVariables.DEFAULT_INSTANCE; + } + + /** + * Detects the GCP platform on which the application is running. + * + * @return the specific GCP platform on which the application is running. + */ + public DetectedPlatform detectPlatform() { + return generateDetectedPlatform(detectSupportedPlatform()); + } + + private SupportedPlatform detectSupportedPlatform() { + if (!isRunningOnGcp()) { + return SupportedPlatform.UNKNOWN_PLATFORM; + } + // Note: Order of detection matters here + if (environmentVariables.get("KUBERNETES_SERVICE_HOST") != null) { + return SupportedPlatform.GOOGLE_KUBERNETES_ENGINE; + } else if (environmentVariables.get("K_CONFIGURATION") != null + && environmentVariables.get("FUNCTION_TARGET") == null) { + return SupportedPlatform.GOOGLE_CLOUD_RUN; + } else if (environmentVariables.get("FUNCTION_TARGET") != null) { + return SupportedPlatform.GOOGLE_CLOUD_FUNCTIONS; + } else if (environmentVariables.get("CLOUD_RUN_JOB") != null) { + return SupportedPlatform.GOOGLE_CLOUD_RUN_JOB; + } else if (environmentVariables.get("GAE_SERVICE") != null) { + return SupportedPlatform.GOOGLE_APP_ENGINE; + } + return SupportedPlatform.GOOGLE_COMPUTE_ENGINE; // default to GCE + } + + private boolean isRunningOnGcp() { + return metadataConfig.getProjectId() != null && !metadataConfig.getProjectId().isEmpty(); + } + + private DetectedPlatform generateDetectedPlatform(SupportedPlatform platform) { + DetectedPlatform detectedPlatform; + switch (platform) { + case GOOGLE_KUBERNETES_ENGINE: + detectedPlatform = new GoogleKubernetesEngine(metadataConfig); + break; + case GOOGLE_CLOUD_RUN: + detectedPlatform = new GoogleCloudRun(environmentVariables, metadataConfig); + break; + case GOOGLE_CLOUD_FUNCTIONS: + detectedPlatform = new GoogleCloudFunction(environmentVariables, metadataConfig); + break; + case GOOGLE_CLOUD_RUN_JOB: + detectedPlatform = new GoogleCloudRunJob(environmentVariables, metadataConfig); + break; + case GOOGLE_APP_ENGINE: + detectedPlatform = new GoogleAppEngine(environmentVariables, metadataConfig); + break; + case GOOGLE_COMPUTE_ENGINE: + detectedPlatform = new GoogleComputeEngine(metadataConfig); + break; + default: + detectedPlatform = new UnknownPlatform(); + } + return detectedPlatform; + } + + /** + * SupportedPlatform represents the GCP platforms that can currently be detected by the + * resource-detector. + */ + enum SupportedPlatform { + /** Represents the Google Compute Engine platform. */ + GOOGLE_COMPUTE_ENGINE, + /** Represents the Google Kubernetes Engine platform. */ + GOOGLE_KUBERNETES_ENGINE, + /** Represents the Google App Engine platform. Could either be flex or standard. */ + GOOGLE_APP_ENGINE, + /** Represents the Google Cloud Run platform (Service). */ + GOOGLE_CLOUD_RUN, + /** Represents the Google Cloud Run platform (Jobs). */ + GOOGLE_CLOUD_RUN_JOB, + /** Represents the Google Cloud Functions platform. */ + GOOGLE_CLOUD_FUNCTIONS, + /** Represents the case when the application is not running on GCP. */ + UNKNOWN_PLATFORM, + } +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleAppEngine.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleAppEngine.java new file mode 100644 index 000000000..79cf0009f --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleAppEngine.java @@ -0,0 +1,64 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +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 java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import javax.annotation.Nullable; + +final class GoogleAppEngine implements DetectedPlatform { + private final EnvironmentVariables environmentVariables; + private final GcpMetadataConfig metadataConfig; + private final Map availableAttributes; + + GoogleAppEngine(EnvironmentVariables environmentVariables, GcpMetadataConfig metadataConfig) { + this.environmentVariables = environmentVariables; + this.metadataConfig = metadataConfig; + this.availableAttributes = prepareAttributes(); + } + + private Map prepareAttributes() { + Map map = new HashMap<>(); + map.put(GAE_MODULE_NAME, this.environmentVariables.get("GAE_SERVICE")); + map.put(GAE_APP_VERSION, this.environmentVariables.get("GAE_VERSION")); + map.put(GAE_INSTANCE_ID, this.environmentVariables.get("GAE_INSTANCE")); + map.put(GAE_AVAILABILITY_ZONE, this.metadataConfig.getZone()); + map.put(GAE_CLOUD_REGION, getCloudRegion()); + return Collections.unmodifiableMap(map); + } + + @Nullable + private String getCloudRegion() { + if (this.environmentVariables.get("GAE_ENV") != null + && this.environmentVariables.get("GAE_ENV").equals("standard")) { + return this.metadataConfig.getRegion(); + } else { + return this.metadataConfig.getRegionFromZone(); + } + } + + @Override + public GcpPlatformDetector.SupportedPlatform getSupportedPlatform() { + return GcpPlatformDetector.SupportedPlatform.GOOGLE_APP_ENGINE; + } + + @Override + public String getProjectId() { + return this.metadataConfig.getProjectId(); + } + + @Override + public Map getAttributes() { + return this.availableAttributes; + } +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleCloudFunction.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleCloudFunction.java new file mode 100644 index 000000000..a3f44fa8e --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleCloudFunction.java @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +class GoogleCloudFunction extends GoogleServerlessCompute { + GoogleCloudFunction(EnvironmentVariables environmentVariables, GcpMetadataConfig metadataConfig) { + super(environmentVariables, metadataConfig); + } + + @Override + public GcpPlatformDetector.SupportedPlatform getSupportedPlatform() { + return GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_FUNCTIONS; + } +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleCloudRun.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleCloudRun.java new file mode 100644 index 000000000..06c34a1ff --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleCloudRun.java @@ -0,0 +1,17 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +class GoogleCloudRun extends GoogleServerlessCompute { + GoogleCloudRun(EnvironmentVariables environmentVariables, GcpMetadataConfig metadataConfig) { + super(environmentVariables, metadataConfig); + } + + @Override + public GcpPlatformDetector.SupportedPlatform getSupportedPlatform() { + return GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN; + } +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleCloudRunJob.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleCloudRunJob.java new file mode 100644 index 000000000..527c38bf1 --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleCloudRunJob.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +final class GoogleCloudRunJob implements DetectedPlatform { + private final GcpMetadataConfig metadataConfig; + private final EnvironmentVariables environmentVariables; + private final Map availableAttributes; + + GoogleCloudRunJob(EnvironmentVariables environmentVariables, GcpMetadataConfig metadataConfig) { + this.metadataConfig = metadataConfig; + this.environmentVariables = environmentVariables; + this.availableAttributes = prepareAttributes(); + } + + private Map prepareAttributes() { + Map map = new HashMap<>(); + map.put(AttributeKeys.SERVERLESS_COMPUTE_NAME, this.environmentVariables.get("CLOUD_RUN_JOB")); + map.put( + AttributeKeys.GCR_JOB_EXECUTION_KEY, this.environmentVariables.get("CLOUD_RUN_EXECUTION")); + map.put( + AttributeKeys.GCR_JOB_TASK_INDEX, this.environmentVariables.get("CLOUD_RUN_TASK_INDEX")); + map.put(AttributeKeys.SERVERLESS_COMPUTE_INSTANCE_ID, this.metadataConfig.getInstanceId()); + map.put(AttributeKeys.SERVERLESS_COMPUTE_CLOUD_REGION, this.metadataConfig.getRegionFromZone()); + return Collections.unmodifiableMap(map); + } + + @Override + public GcpPlatformDetector.SupportedPlatform getSupportedPlatform() { + return GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN_JOB; + } + + @Override + public String getProjectId() { + return this.metadataConfig.getProjectId(); + } + + @Override + public Map getAttributes() { + return this.availableAttributes; + } +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleComputeEngine.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleComputeEngine.java new file mode 100644 index 000000000..6d003c971 --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleComputeEngine.java @@ -0,0 +1,53 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +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 java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +final class GoogleComputeEngine implements DetectedPlatform { + private final GcpMetadataConfig metadataConfig; + private final Map availableAttributes; + + GoogleComputeEngine(GcpMetadataConfig metadataConfig) { + this.metadataConfig = metadataConfig; + this.availableAttributes = prepareAttributes(); + } + + private Map prepareAttributes() { + Map map = new HashMap<>(); + map.put(GCE_AVAILABILITY_ZONE, this.metadataConfig.getZone()); + map.put(GCE_CLOUD_REGION, this.metadataConfig.getRegionFromZone()); + map.put(GCE_INSTANCE_ID, this.metadataConfig.getInstanceId()); + map.put(GCE_INSTANCE_NAME, this.metadataConfig.getInstanceName()); + map.put(GCE_INSTANCE_HOSTNAME, this.metadataConfig.getInstanceHostName()); + map.put(GCE_MACHINE_TYPE, this.metadataConfig.getMachineType()); + return Collections.unmodifiableMap(map); + } + + @Override + public GcpPlatformDetector.SupportedPlatform getSupportedPlatform() { + return GcpPlatformDetector.SupportedPlatform.GOOGLE_COMPUTE_ENGINE; + } + + @Override + public String getProjectId() { + return this.metadataConfig.getProjectId(); + } + + @Override + public Map getAttributes() { + return this.availableAttributes; + } +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleKubernetesEngine.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleKubernetesEngine.java new file mode 100644 index 000000000..8ad151457 --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleKubernetesEngine.java @@ -0,0 +1,65 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +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 java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +final class GoogleKubernetesEngine implements DetectedPlatform { + private final GcpMetadataConfig metadataConfig; + private final Map availableAttributes; + + GoogleKubernetesEngine(GcpMetadataConfig metadataConfig) { + this.metadataConfig = metadataConfig; + this.availableAttributes = prepareAttributes(); + } + + private Map prepareAttributes() { + Map map = new HashMap<>(); + map.put(GKE_CLUSTER_NAME, this.metadataConfig.getClusterName()); + map.put(GKE_CLUSTER_LOCATION, this.metadataConfig.getClusterLocation()); + map.put(GKE_CLUSTER_LOCATION_TYPE, this.getClusterLocationType()); + map.put(GKE_HOST_ID, this.metadataConfig.getInstanceId()); + return Collections.unmodifiableMap(map); + } + + private String getClusterLocationType() { + String clusterLocation = this.metadataConfig.getClusterLocation(); + long dashCount = + (clusterLocation == null || clusterLocation.isEmpty()) + ? 0 + : clusterLocation.chars().filter(ch -> ch == '-').count(); + if (dashCount == 1) { + return GKE_LOCATION_TYPE_REGION; + } else if (dashCount == 2) { + return GKE_LOCATION_TYPE_ZONE; + } + return ""; + } + + @Override + public GcpPlatformDetector.SupportedPlatform getSupportedPlatform() { + return GcpPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE; + } + + @Override + public String getProjectId() { + return this.metadataConfig.getProjectId(); + } + + @Override + public Map getAttributes() { + return this.availableAttributes; + } +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleServerlessCompute.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleServerlessCompute.java new file mode 100644 index 000000000..14d5f468f --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/GoogleServerlessCompute.java @@ -0,0 +1,47 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * GoogleServerlessCompute adds attributes applicable to all serverless compute platforms in GCP. + * Currently, this includes Google Cloud Functions & Google Cloud Run. + */ +abstract class GoogleServerlessCompute implements DetectedPlatform { + private final EnvironmentVariables environmentVariables; + private final GcpMetadataConfig metadataConfig; + private final Map availableAttributes; + + GoogleServerlessCompute( + EnvironmentVariables environmentVariables, GcpMetadataConfig metadataConfig) { + this.environmentVariables = environmentVariables; + this.metadataConfig = metadataConfig; + this.availableAttributes = prepareAttributes(); + } + + private Map prepareAttributes() { + Map map = new HashMap<>(); + map.put(AttributeKeys.SERVERLESS_COMPUTE_NAME, this.environmentVariables.get("K_SERVICE")); + map.put(AttributeKeys.SERVERLESS_COMPUTE_REVISION, this.environmentVariables.get("K_REVISION")); + map.put(AttributeKeys.SERVERLESS_COMPUTE_AVAILABILITY_ZONE, this.metadataConfig.getZone()); + map.put(AttributeKeys.SERVERLESS_COMPUTE_CLOUD_REGION, this.metadataConfig.getRegionFromZone()); + map.put(AttributeKeys.SERVERLESS_COMPUTE_INSTANCE_ID, this.metadataConfig.getInstanceId()); + return Collections.unmodifiableMap(map); + } + + @Override + public String getProjectId() { + return this.metadataConfig.getProjectId(); + } + + @Override + public Map getAttributes() { + return this.availableAttributes; + } +} diff --git a/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/UnknownPlatform.java b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/UnknownPlatform.java new file mode 100644 index 000000000..b360f1f05 --- /dev/null +++ b/gcp-resources/src/main/java/io/opentelemetry/contrib/gcp/resource/UnknownPlatform.java @@ -0,0 +1,29 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +import java.util.Collections; +import java.util.Map; + +class UnknownPlatform implements DetectedPlatform { + + UnknownPlatform() {} + + @Override + public GcpPlatformDetector.SupportedPlatform getSupportedPlatform() { + return GcpPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM; + } + + @Override + public String getProjectId() { + return ""; + } + + @Override + public Map getAttributes() { + return Collections.emptyMap(); + } +} diff --git a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/EnvVarMock.java b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/EnvVarMock.java new file mode 100644 index 000000000..494c7b715 --- /dev/null +++ b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/EnvVarMock.java @@ -0,0 +1,21 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +import java.util.Map; + +class EnvVarMock implements EnvironmentVariables { + private final Map mock; + + public EnvVarMock(Map mock) { + this.mock = mock; + } + + @Override + public String get(String key) { + return mock.get(key); + } +} diff --git a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java index 9314a1b67..d1e99a433 100644 --- a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java +++ b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GCPResourceProviderTest.java @@ -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.GCP_CLOUD_RUN_JOB_TASK_INDEX; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; import static io.opentelemetry.semconv.incubating.CloudIncubatingAttributes.CLOUD_ACCOUNT_ID; @@ -55,8 +55,6 @@ import static org.assertj.core.api.Assertions.fail; import static org.mockito.Mockito.verify; -import com.google.cloud.opentelemetry.detection.DetectedPlatform; -import com.google.cloud.opentelemetry.detection.GCPPlatformDetector; import com.google.common.collect.ImmutableMap; import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; @@ -90,7 +88,7 @@ private static DetectedPlatform generateMockGcePlatform() { GCE_INSTANCE_HOSTNAME, "instance-hostname")); DetectedPlatform mockGCEPlatform = Mockito.mock(DetectedPlatform.class); Mockito.when(mockGCEPlatform.getSupportedPlatform()) - .thenReturn(GCPPlatformDetector.SupportedPlatform.GOOGLE_COMPUTE_ENGINE); + .thenReturn(GcpPlatformDetector.SupportedPlatform.GOOGLE_COMPUTE_ENGINE); Mockito.when(mockGCEPlatform.getAttributes()).thenReturn(mockAttributes); Mockito.when(mockGCEPlatform.getProjectId()).thenReturn(DUMMY_PROJECT_ID); return mockGCEPlatform; @@ -107,18 +105,18 @@ private DetectedPlatform generateMockGkePlatform(String gkeClusterLocationType) DetectedPlatform mockGKEPlatform = Mockito.mock(DetectedPlatform.class); Mockito.when(mockGKEPlatform.getSupportedPlatform()) - .thenReturn(GCPPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE); + .thenReturn(GcpPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE); Mockito.when(mockGKEPlatform.getAttributes()).thenReturn(mockAttributes); Mockito.when(mockGKEPlatform.getProjectId()).thenReturn(DUMMY_PROJECT_ID); return mockGKEPlatform; } private static DetectedPlatform generateMockServerlessPlatform( - GCPPlatformDetector.SupportedPlatform platform) { - EnumSet serverlessPlatforms = + GcpPlatformDetector.SupportedPlatform platform) { + EnumSet serverlessPlatforms = EnumSet.of( - GCPPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN, - GCPPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_FUNCTIONS); + GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN, + GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_FUNCTIONS); if (!serverlessPlatforms.contains(platform)) { throw new IllegalArgumentException(); } @@ -148,7 +146,7 @@ private static DetectedPlatform generateMockGcrJobPlatform() { GCR_JOB_EXECUTION_KEY, "serverless-job-a1b2c3")); DetectedPlatform mockServerlessPlatform = Mockito.mock(DetectedPlatform.class); Mockito.when(mockServerlessPlatform.getSupportedPlatform()) - .thenReturn(GCPPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN_JOB); + .thenReturn(GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN_JOB); Mockito.when(mockServerlessPlatform.getAttributes()).thenReturn(mockAttributes); Mockito.when(mockServerlessPlatform.getProjectId()).thenReturn(DUMMY_PROJECT_ID); return mockServerlessPlatform; @@ -165,7 +163,7 @@ private static DetectedPlatform generateMockGaePlatform() { GAE_AVAILABILITY_ZONE, "us-central1-b")); DetectedPlatform mockGAEPlatform = Mockito.mock(DetectedPlatform.class); Mockito.when(mockGAEPlatform.getSupportedPlatform()) - .thenReturn(GCPPlatformDetector.SupportedPlatform.GOOGLE_APP_ENGINE); + .thenReturn(GcpPlatformDetector.SupportedPlatform.GOOGLE_APP_ENGINE); Mockito.when(mockGAEPlatform.getAttributes()).thenReturn(mockAttributes); Mockito.when(mockGAEPlatform.getProjectId()).thenReturn(DUMMY_PROJECT_ID); return mockGAEPlatform; @@ -180,14 +178,14 @@ private static DetectedPlatform generateMockUnknownPlatform() { DetectedPlatform mockUnknownPlatform = Mockito.mock(DetectedPlatform.class); Mockito.when(mockUnknownPlatform.getSupportedPlatform()) - .thenReturn(GCPPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM); + .thenReturn(GcpPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM); Mockito.when(mockUnknownPlatform.getAttributes()).thenReturn(mockAttributes); return mockUnknownPlatform; } @Test void testGceResourceAttributesMapping() { - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGcePlatform(); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); Map detectedAttributes = mockPlatform.getAttributes(); @@ -211,7 +209,7 @@ void testGceResourceAttributesMapping() { @Test void testGkeResourceAttributesMapping_LocationTypeRegion() { - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGkePlatform(GKE_LOCATION_TYPE_REGION); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -228,7 +226,7 @@ void testGkeResourceAttributesMapping_LocationTypeRegion() { @Test void testGkeResourceAttributesMapping_LocationTypeZone() { - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGkePlatform(GKE_LOCATION_TYPE_ZONE); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -250,10 +248,10 @@ void testGkeResourceAttributesMapping_LocationTypeInvalid() { mockGKEAttributes.put(GKE_CLUSTER_LOCATION_TYPE, "INVALID"); mockGKEAttributes.put(GKE_CLUSTER_LOCATION, "some-location"); - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = Mockito.mock(DetectedPlatform.class); Mockito.when(mockPlatform.getSupportedPlatform()) - .thenReturn(GCPPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE); + .thenReturn(GcpPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE); Mockito.when(mockPlatform.getProjectId()).thenReturn(DUMMY_PROJECT_ID); Mockito.when(mockPlatform.getAttributes()).thenReturn(mockGKEAttributes); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -271,7 +269,7 @@ void testGkeResourceAttributesMapping_LocationTypeInvalid() { @Test void testGkeResourceAttributesMapping_LocationMissing() { - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGkePlatform(""); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); @@ -296,9 +294,9 @@ private static void verifyGkeMapping(Resource gotResource, DetectedPlatform dete @Test void testGcrServiceResourceAttributesMapping() { - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = - generateMockServerlessPlatform(GCPPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN); + generateMockServerlessPlatform(GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); Resource gotResource = new GCPResourceProvider(mockDetector).createResource(mockConfigProps); @@ -313,10 +311,10 @@ void testGcrServiceResourceAttributesMapping() { @Test void testGcfResourceAttributeMapping() { - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = generateMockServerlessPlatform( - GCPPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_FUNCTIONS); + GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_FUNCTIONS); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); Resource gotResource = new GCPResourceProvider(mockDetector).createResource(mockConfigProps); @@ -344,7 +342,7 @@ private static void verifyServerlessMapping( @Test void testGcrJobResourceAttributesMapping() { - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGcrJobPlatform(); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); Map detectedAttributes = mockPlatform.getAttributes(); @@ -369,7 +367,7 @@ void testGcrJobResourceAttributesMapping() { @Test void testGaeResourceAttributeMapping() { - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = generateMockGaePlatform(); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); Map detectedAttributes = mockPlatform.getAttributes(); @@ -391,7 +389,7 @@ void testGaeResourceAttributeMapping() { @Test void testUnknownPlatformResourceAttributesMapping() { - GCPPlatformDetector mockDetector = Mockito.mock(GCPPlatformDetector.class); + GcpPlatformDetector mockDetector = Mockito.mock(GcpPlatformDetector.class); DetectedPlatform mockPlatform = generateMockUnknownPlatform(); Mockito.when(mockDetector.detectPlatform()).thenReturn(mockPlatform); diff --git a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GcpMetadataConfigTest.java b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GcpMetadataConfigTest.java new file mode 100644 index 000000000..b98a8e9e9 --- /dev/null +++ b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GcpMetadataConfigTest.java @@ -0,0 +1,141 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +@WireMockTest(httpPort = 8090) +class GcpMetadataConfigTest { + private static final String mockProjectId = "pid"; + private static final String mockZone = "country-region-zone"; + private static final String mockRegion = "country-region1"; + private static final String mockInstanceId = "instance-id"; + private static final String mockInstanceName = "instance-name"; + private static final String mockInstanceType = "instance-type"; + private static final String mockClusterName = "cluster-name"; + private static final String mockClusterLocation = "cluster-location"; + private static final String mockHostname = "hostname"; + + private final GcpMetadataConfig mockMetadataConfig = + new GcpMetadataConfig("http://localhost:8090/"); + + @BeforeEach + public void setupMockMetadataConfig() { + TestUtils.stubEndpoint("/project/project-id", mockProjectId); + TestUtils.stubEndpoint("/instance/zone", mockZone); + TestUtils.stubEndpoint("/instance/region", mockRegion); + TestUtils.stubEndpoint("/instance/id", mockInstanceId); + TestUtils.stubEndpoint("/instance/name", mockInstanceName); + TestUtils.stubEndpoint("/instance/machine-type", mockInstanceType); + TestUtils.stubEndpoint("/instance/attributes/cluster-name", mockClusterName); + TestUtils.stubEndpoint("/instance/attributes/cluster-location", mockClusterLocation); + TestUtils.stubEndpoint("/instance/hostname", mockHostname); + } + + @Test + void testGetProjectId() { + assertEquals(mockProjectId, mockMetadataConfig.getProjectId()); + } + + /** Test Zone Retrieval */ + @ParameterizedTest + @MethodSource("provideZoneRetrievalArguments") + void testGetZone(String stubbedMockZone, String expectedMockZone) { + TestUtils.stubEndpoint("/instance/zone", stubbedMockZone); + assertEquals(expectedMockZone, mockMetadataConfig.getZone()); + } + + private static Stream provideZoneRetrievalArguments() { + return Stream.of( + Arguments.of(mockZone, mockZone), + Arguments.of( + "projects/640212054955/zones/australia-southeast1-a", "australia-southeast1-a"), + Arguments.of("", null), + Arguments.of(null, null)); + } + + /** Test Region Retrieval */ + @ParameterizedTest + @MethodSource("provideRegionRetrievalArguments") + void testGetRegion(String stubbedMockRegion, String expectedMockRegion) { + TestUtils.stubEndpoint("/instance/region", stubbedMockRegion); + assertEquals(expectedMockRegion, mockMetadataConfig.getRegion()); + } + + private static Stream provideRegionRetrievalArguments() { + return Stream.of( + Arguments.of(mockRegion, mockRegion), + Arguments.of("projects/640212054955/regions/us-central1", "us-central1"), + Arguments.of("", null), + Arguments.of(null, null)); + } + + /** Test Region Retrieval from Zone */ + @ParameterizedTest + @MethodSource("provideZoneArguments") + void testGetRegionFromZone(String stubbedMockZone, String expectedRegion) { + TestUtils.stubEndpoint("/instance/zone", stubbedMockZone); + assertEquals(expectedRegion, mockMetadataConfig.getRegionFromZone()); + } + + private static Stream provideZoneArguments() { + return Stream.of( + Arguments.of(mockZone, "country-region"), + Arguments.of("projects/640212054955/zones/australia-southeast1-a", "australia-southeast1"), + Arguments.of("country-region", null), + Arguments.of("", null), + Arguments.of(null, null)); + } + + /** Test Machine Type Retrieval */ + @ParameterizedTest + @MethodSource("provideMachineTypeRetrievalArguments") + void testGetMachineType(String stubbedMockMachineType, String expectedMockMachineType) { + TestUtils.stubEndpoint("/instance/machine-type", stubbedMockMachineType); + assertEquals(expectedMockMachineType, mockMetadataConfig.getMachineType()); + } + + private static Stream provideMachineTypeRetrievalArguments() { + return Stream.of( + Arguments.of(mockInstanceType, mockInstanceType), + Arguments.of("projects/640212054955/machineTypes/e2-medium", "e2-medium"), + Arguments.of("", null), + Arguments.of(null, null)); + } + + @Test + void testGetInstanceId() { + assertEquals(mockInstanceId, mockMetadataConfig.getInstanceId()); + } + + @Test + void testGetClusterName() { + assertEquals(mockClusterName, mockMetadataConfig.getClusterName()); + } + + @Test + void testGetClusterLocation() { + assertEquals(mockClusterLocation, mockMetadataConfig.getClusterLocation()); + } + + @Test + void testGetInstanceHostName() { + assertEquals(mockHostname, mockMetadataConfig.getInstanceHostName()); + } + + @Test + void testGetInstanceName() { + assertEquals(mockInstanceName, mockMetadataConfig.getInstanceName()); + } +} diff --git a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GcpPlatformDetectorTest.java b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GcpPlatformDetectorTest.java new file mode 100644 index 000000000..b3c5e75cd --- /dev/null +++ b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/GcpPlatformDetectorTest.java @@ -0,0 +1,426 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; +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 org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +import com.github.tomakehurst.wiremock.junit5.WireMockTest; +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import java.util.stream.Stream; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; +import org.mockito.Mockito; + +@WireMockTest(httpPort = 8089) +class GcpPlatformDetectorTest { + private final GcpMetadataConfig mockMetadataConfig = + new GcpMetadataConfig("http://localhost:8089/"); + private static final Map envVars = new HashMap<>(); + + @BeforeEach + void setup() { + envVars.clear(); + } + + @ParameterizedTest + @NullSource + @ValueSource(strings = {""}) + void testGCPComputeResourceNotGCP(String projectId) { + GcpMetadataConfig mockMetadataConfig = Mockito.mock(GcpMetadataConfig.class); + Mockito.when(mockMetadataConfig.getProjectId()).thenReturn(projectId); + + GcpPlatformDetector detector = + new GcpPlatformDetector(mockMetadataConfig, EnvironmentVariables.DEFAULT_INSTANCE); + // If GcpMetadataConfig cannot find ProjectId, then the platform should be unsupported + assertEquals( + GcpPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM, + detector.detectPlatform().getSupportedPlatform()); + assertEquals(Collections.emptyMap(), detector.detectPlatform().getAttributes()); + } + + @Test + void testGCPComputeResourceNonGCPEndpoint() { + // intentionally not providing the required Metadata-Flavor header with the + // request to mimic non GCP endpoint + stubFor( + get(urlEqualTo("/project/project-id")) + .willReturn(aResponse().withBody("nonGCPEndpointTest"))); + GcpPlatformDetector detector = + new GcpPlatformDetector(mockMetadataConfig, EnvironmentVariables.DEFAULT_INSTANCE); + assertEquals( + GcpPlatformDetector.SupportedPlatform.UNKNOWN_PLATFORM, + detector.detectPlatform().getSupportedPlatform()); + assertEquals(Collections.emptyMap(), detector.detectPlatform().getAttributes()); + } + + /** Google Compute Engine Tests * */ + @Test + void testGCEResourceWithGCEAttributesSucceeds() { + TestUtils.stubEndpoint("/project/project-id", "GCE-pid"); + TestUtils.stubEndpoint("/instance/zone", "country-gce_region-gce_zone"); + TestUtils.stubEndpoint("/instance/id", "GCE-instance-id"); + TestUtils.stubEndpoint("/instance/name", "GCE-instance-name"); + TestUtils.stubEndpoint("/instance/machine-type", "GCE-instance-type"); + TestUtils.stubEndpoint("/instance/hostname", "GCE-instance-hostname"); + + GcpPlatformDetector detector = + new GcpPlatformDetector(mockMetadataConfig, new EnvVarMock(envVars)); + + assertEquals( + GcpPlatformDetector.SupportedPlatform.GOOGLE_COMPUTE_ENGINE, + detector.detectPlatform().getSupportedPlatform()); + assertEquals("GCE-pid", detector.detectPlatform().getProjectId()); + Map detectedAttributes = detector.detectPlatform().getAttributes(); + assertEquals(new GoogleComputeEngine(mockMetadataConfig).getAttributes(), detectedAttributes); + assertEquals(6, detectedAttributes.size()); + + assertEquals("country-gce_region-gce_zone", detectedAttributes.get(GCE_AVAILABILITY_ZONE)); + assertEquals("country-gce_region", detectedAttributes.get(GCE_CLOUD_REGION)); + assertEquals("GCE-instance-id", detectedAttributes.get(GCE_INSTANCE_ID)); + assertEquals("GCE-instance-name", detectedAttributes.get(GCE_INSTANCE_NAME)); + assertEquals("GCE-instance-type", detectedAttributes.get(GCE_MACHINE_TYPE)); + assertEquals("GCE-instance-hostname", detectedAttributes.get(GCE_INSTANCE_HOSTNAME)); + } + + /** Google Kubernetes Engine Tests * */ + @Test + void testGKEResourceWithGKEAttributesSucceedsLocationZone() { + envVars.put("KUBERNETES_SERVICE_HOST", "GKE-testHost"); + envVars.put("NAMESPACE", "GKE-testNameSpace"); + // Hostname can truncate pod name, so we test downward API override. + envVars.put("HOSTNAME", "GKE-testHostName"); + envVars.put("POD_NAME", "GKE-testHostName-full-1234"); + envVars.put("CONTAINER_NAME", "GKE-testContainerName"); + + TestUtils.stubEndpoint("/project/project-id", "GKE-pid"); + TestUtils.stubEndpoint("/instance/id", "GKE-instance-id"); + TestUtils.stubEndpoint("/instance/name", "instance-name"); + TestUtils.stubEndpoint("/instance/machine-type", "instance-type"); + TestUtils.stubEndpoint("/instance/attributes/cluster-name", "GKE-cluster-name"); + TestUtils.stubEndpoint("/instance/attributes/cluster-location", "country-region-zone"); + + EnvironmentVariables mockEnv = new EnvVarMock(envVars); + GcpPlatformDetector detector = new GcpPlatformDetector(mockMetadataConfig, mockEnv); + + Map detectedAttributes = detector.detectPlatform().getAttributes(); + assertEquals( + GcpPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE, + detector.detectPlatform().getSupportedPlatform()); + assertEquals( + new GoogleKubernetesEngine(mockMetadataConfig).getAttributes(), detectedAttributes); + assertEquals("GKE-pid", detector.detectPlatform().getProjectId()); + assertEquals(4, detectedAttributes.size()); + + assertEquals(GKE_LOCATION_TYPE_ZONE, detectedAttributes.get(GKE_CLUSTER_LOCATION_TYPE)); + assertEquals("country-region-zone", detectedAttributes.get(GKE_CLUSTER_LOCATION)); + assertEquals("GKE-cluster-name", detectedAttributes.get(GKE_CLUSTER_NAME)); + assertEquals("GKE-instance-id", detectedAttributes.get(GKE_HOST_ID)); + } + + @Test + void testGKEResourceWithGKEAttributesSucceedsLocationRegion() { + envVars.put("KUBERNETES_SERVICE_HOST", "GKE-testHost"); + envVars.put("NAMESPACE", "GKE-testNameSpace"); + // Hostname can truncate pod name, so we test downward API override. + envVars.put("HOSTNAME", "GKE-testHostName"); + envVars.put("POD_NAME", "GKE-testHostName-full-1234"); + envVars.put("CONTAINER_NAME", "GKE-testContainerName"); + + TestUtils.stubEndpoint("/project/project-id", "GKE-pid"); + TestUtils.stubEndpoint("/instance/id", "GKE-instance-id"); + TestUtils.stubEndpoint("/instance/name", "GCE-instance-name"); + TestUtils.stubEndpoint("/instance/machine-type", "GKE-instance-type"); + TestUtils.stubEndpoint("/instance/attributes/cluster-name", "GKE-cluster-name"); + TestUtils.stubEndpoint("/instance/attributes/cluster-location", "country-region"); + + EnvironmentVariables mockEnv = new EnvVarMock(envVars); + GcpPlatformDetector detector = new GcpPlatformDetector(mockMetadataConfig, mockEnv); + + Map detectedAttributes = detector.detectPlatform().getAttributes(); + assertEquals( + GcpPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE, + detector.detectPlatform().getSupportedPlatform()); + assertEquals( + new GoogleKubernetesEngine(mockMetadataConfig).getAttributes(), detectedAttributes); + assertEquals("GKE-pid", detector.detectPlatform().getProjectId()); + assertEquals(4, detectedAttributes.size()); + + assertEquals(GKE_LOCATION_TYPE_REGION, detectedAttributes.get(GKE_CLUSTER_LOCATION_TYPE)); + assertEquals("country-region", detectedAttributes.get(GKE_CLUSTER_LOCATION)); + assertEquals("GKE-cluster-name", detectedAttributes.get(GKE_CLUSTER_NAME)); + assertEquals("GKE-instance-id", detectedAttributes.get(GKE_HOST_ID)); + } + + @ParameterizedTest + @NullSource + @ValueSource(strings = {"", "country", "country-region-zone-invalid"}) + void testGKEResourceDetectionWithInvalidLocations(String clusterLocation) { + envVars.put("KUBERNETES_SERVICE_HOST", "GKE-testHost"); + envVars.put("NAMESPACE", "GKE-testNameSpace"); + // Hostname can truncate pod name, so we test downward API override. + envVars.put("HOSTNAME", "GKE-testHostName"); + envVars.put("POD_NAME", "GKE-testHostName-full-1234"); + envVars.put("CONTAINER_NAME", "GKE-testContainerName"); + + TestUtils.stubEndpoint("/project/project-id", "GKE-pid"); + TestUtils.stubEndpoint("/instance/id", "GKE-instance-id"); + TestUtils.stubEndpoint("/instance/name", "GKE-instance-name"); + TestUtils.stubEndpoint("/instance/machine-type", "GKE-instance-type"); + TestUtils.stubEndpoint("/instance/attributes/cluster-name", "GKE-cluster-name"); + TestUtils.stubEndpoint("/instance/attributes/cluster-location", clusterLocation); + + EnvironmentVariables mockEnv = new EnvVarMock(envVars); + GcpPlatformDetector detector = new GcpPlatformDetector(mockMetadataConfig, mockEnv); + + Map detectedAttributes = detector.detectPlatform().getAttributes(); + assertEquals( + GcpPlatformDetector.SupportedPlatform.GOOGLE_KUBERNETES_ENGINE, + detector.detectPlatform().getSupportedPlatform()); + assertEquals( + new GoogleKubernetesEngine(mockMetadataConfig).getAttributes(), detectedAttributes); + assertEquals("GKE-pid", detector.detectPlatform().getProjectId()); + assertEquals(4, detectedAttributes.size()); + + assertEquals("", detector.detectPlatform().getAttributes().get(GKE_CLUSTER_LOCATION_TYPE)); + if (clusterLocation == null || clusterLocation.isEmpty()) { + assertNull(detectedAttributes.get(GKE_CLUSTER_LOCATION)); + } else { + assertEquals(clusterLocation, detectedAttributes.get(GKE_CLUSTER_LOCATION)); + } + assertEquals("GKE-cluster-name", detectedAttributes.get(GKE_CLUSTER_NAME)); + assertEquals("GKE-instance-id", detectedAttributes.get(GKE_HOST_ID)); + } + + /** Google Cloud Functions Tests * */ + @Test + void testGCFResourceWithCloudFunctionAttributesSucceeds() { + // Setup GCF required env vars + envVars.put("K_SERVICE", "cloud-function-hello"); + envVars.put("K_REVISION", "cloud-function-hello.1"); + envVars.put("FUNCTION_TARGET", "cloud-function-hello"); + + TestUtils.stubEndpoint("/project/project-id", "GCF-pid"); + TestUtils.stubEndpoint("/instance/zone", "country-region-zone"); + TestUtils.stubEndpoint("/instance/id", "GCF-instance-id"); + + EnvironmentVariables mockEnv = new EnvVarMock(envVars); + GcpPlatformDetector detector = new GcpPlatformDetector(mockMetadataConfig, mockEnv); + + Map detectedAttributes = detector.detectPlatform().getAttributes(); + assertEquals( + GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_FUNCTIONS, + detector.detectPlatform().getSupportedPlatform()); + assertEquals( + new GoogleCloudFunction(mockEnv, mockMetadataConfig).getAttributes(), detectedAttributes); + assertEquals("GCF-pid", detector.detectPlatform().getProjectId()); + assertEquals(5, detectedAttributes.size()); + + assertEquals("cloud-function-hello", detectedAttributes.get(SERVERLESS_COMPUTE_NAME)); + assertEquals("cloud-function-hello.1", detectedAttributes.get(SERVERLESS_COMPUTE_REVISION)); + assertEquals( + "country-region-zone", detectedAttributes.get(SERVERLESS_COMPUTE_AVAILABILITY_ZONE)); + assertEquals("country-region", detectedAttributes.get(SERVERLESS_COMPUTE_CLOUD_REGION)); + assertEquals("GCF-instance-id", detectedAttributes.get(SERVERLESS_COMPUTE_INSTANCE_ID)); + } + + @Test + void testGCFDetectionWhenGCRAttributesPresent() { + // Setup GCF required env vars + envVars.put("K_SERVICE", "cloud-function-hello"); + envVars.put("K_REVISION", "cloud-function-hello.1"); + envVars.put("FUNCTION_TARGET", "cloud-function-hello"); + // This should be ignored and detected platform should still be GCF + envVars.put("K_CONFIGURATION", "cloud-run-hello"); + + TestUtils.stubEndpoint("/project/project-id", "GCF-pid"); + TestUtils.stubEndpoint("/instance/zone", "country-region-zone"); + TestUtils.stubEndpoint("/instance/id", "GCF-instance-id"); + + EnvironmentVariables mockEnv = new EnvVarMock(envVars); + GcpPlatformDetector detector = new GcpPlatformDetector(mockMetadataConfig, mockEnv); + + assertEquals( + GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_FUNCTIONS, + detector.detectPlatform().getSupportedPlatform()); + assertEquals("GCF-pid", detector.detectPlatform().getProjectId()); + assertEquals( + new GoogleCloudFunction(mockEnv, mockMetadataConfig).getAttributes(), + detector.detectPlatform().getAttributes()); + } + + /** Google Cloud Run Tests (Service) * */ + @Test + void testGCFResourceWithCloudRunAttributesSucceeds() { + // Setup GCR service required env vars + envVars.put("K_SERVICE", "cloud-run-hello"); + envVars.put("K_REVISION", "cloud-run-hello.1"); + envVars.put("K_CONFIGURATION", "cloud-run-hello"); + + TestUtils.stubEndpoint("/project/project-id", "GCR-pid"); + TestUtils.stubEndpoint("/instance/zone", "country-region-zone"); + TestUtils.stubEndpoint("/instance/id", "GCR-instance-id"); + + EnvironmentVariables mockEnv = new EnvVarMock(envVars); + GcpPlatformDetector detector = new GcpPlatformDetector(mockMetadataConfig, mockEnv); + + Map detectedAttributes = detector.detectPlatform().getAttributes(); + assertEquals( + GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN, + detector.detectPlatform().getSupportedPlatform()); + assertEquals( + new GoogleCloudFunction(mockEnv, mockMetadataConfig).getAttributes(), detectedAttributes); + assertEquals("GCR-pid", detector.detectPlatform().getProjectId()); + assertEquals(5, detectedAttributes.size()); + + assertEquals("cloud-run-hello", detectedAttributes.get(SERVERLESS_COMPUTE_NAME)); + assertEquals("cloud-run-hello.1", detectedAttributes.get(SERVERLESS_COMPUTE_REVISION)); + assertEquals( + "country-region-zone", detectedAttributes.get(SERVERLESS_COMPUTE_AVAILABILITY_ZONE)); + assertEquals("country-region", detectedAttributes.get(SERVERLESS_COMPUTE_CLOUD_REGION)); + assertEquals("GCR-instance-id", detectedAttributes.get(SERVERLESS_COMPUTE_INSTANCE_ID)); + } + + /** Google Cloud Run Tests (Jobs) * */ + @Test + void testCloudRunJobResourceWithAttributesSucceeds() { + // Setup GCR Job required env vars + envVars.put("CLOUD_RUN_JOB", "cloud-run-hello-job"); + envVars.put("CLOUD_RUN_EXECUTION", "cloud-run-hello-job-1a2b3c"); + envVars.put("CLOUD_RUN_TASK_INDEX", "0"); + + TestUtils.stubEndpoint("/project/project-id", "GCR-pid"); + TestUtils.stubEndpoint("/instance/zone", "country-region-zone"); + TestUtils.stubEndpoint("/instance/id", "GCR-job-instance-id"); + + EnvironmentVariables mockEnv = new EnvVarMock(envVars); + GcpPlatformDetector detector = new GcpPlatformDetector(mockMetadataConfig, mockEnv); + + Map detectedAttributes = detector.detectPlatform().getAttributes(); + assertEquals( + GcpPlatformDetector.SupportedPlatform.GOOGLE_CLOUD_RUN_JOB, + detector.detectPlatform().getSupportedPlatform()); + assertEquals( + new GoogleCloudRunJob(mockEnv, mockMetadataConfig).getAttributes(), detectedAttributes); + assertEquals("GCR-pid", detector.detectPlatform().getProjectId()); + assertEquals(5, detectedAttributes.size()); + + assertEquals("cloud-run-hello-job-1a2b3c", detectedAttributes.get(GCR_JOB_EXECUTION_KEY)); + assertEquals("0", detectedAttributes.get(GCR_JOB_TASK_INDEX)); + assertEquals("cloud-run-hello-job", detectedAttributes.get(SERVERLESS_COMPUTE_NAME)); + assertEquals("country-region", detectedAttributes.get(SERVERLESS_COMPUTE_CLOUD_REGION)); + assertEquals("GCR-job-instance-id", detectedAttributes.get(SERVERLESS_COMPUTE_INSTANCE_ID)); + } + + /** Google App Engine Tests * */ + @ParameterizedTest + @MethodSource("provideGAEVariantEnvironmentVariable") + void testGAEResourceWithAppEngineAttributesSucceeds( + String gaeEnvironmentVar, + String metadataZone, + String expectedZone, + String metadataRegion, + String expectedRegion) { + envVars.put("GAE_SERVICE", "app-engine-hello"); + envVars.put("GAE_VERSION", "app-engine-hello-v1"); + envVars.put("GAE_INSTANCE", "app-engine-hello-f236d"); + envVars.put("GAE_ENV", gaeEnvironmentVar); + + TestUtils.stubEndpoint("/project/project-id", "GAE-pid"); + TestUtils.stubEndpoint("/instance/zone", metadataZone); + TestUtils.stubEndpoint("/instance/region", metadataRegion); + TestUtils.stubEndpoint("/instance/id", "GAE-instance-id"); + + EnvironmentVariables mockEnv = new EnvVarMock(envVars); + GcpPlatformDetector detector = new GcpPlatformDetector(mockMetadataConfig, mockEnv); + + Map detectedAttributes = detector.detectPlatform().getAttributes(); + assertEquals( + GcpPlatformDetector.SupportedPlatform.GOOGLE_APP_ENGINE, + detector.detectPlatform().getSupportedPlatform()); + assertEquals( + new GoogleAppEngine(mockEnv, mockMetadataConfig).getAttributes(), detectedAttributes); + assertEquals("GAE-pid", detector.detectPlatform().getProjectId()); + assertEquals(5, detectedAttributes.size()); + assertEquals(expectedRegion, detector.detectPlatform().getAttributes().get(GAE_CLOUD_REGION)); + assertEquals("app-engine-hello", detectedAttributes.get(GAE_MODULE_NAME)); + assertEquals("app-engine-hello-v1", detectedAttributes.get(GAE_APP_VERSION)); + assertEquals("app-engine-hello-f236d", detectedAttributes.get(GAE_INSTANCE_ID)); + assertEquals(expectedZone, detectedAttributes.get(GAE_AVAILABILITY_ZONE)); + } + + private static Arguments gaeTestArguments( + String gaeEnvironmentVar, + String metadataZone, + String expectedZone, + String metadataRegion, + String expectedRegion) { + return Arguments.of( + gaeEnvironmentVar, metadataZone, expectedZone, metadataRegion, expectedRegion); + } + + // Provides parameterized arguments for testGAEResourceWithAppEngineAttributesSucceeds + private static Stream provideGAEVariantEnvironmentVariable() { + + return Stream.of( + // GAE standard + gaeTestArguments( + "standard", + // the zone should be extracted from the zone attribute + "projects/233510669999/zones/us15", + "us15", + // the region should be extracted from region attribute + "projects/233510669999/regions/us-central1", + "us-central1"), + + // GAE flex + gaeTestArguments( + (String) null, + "country-region-zone", + "country-region-zone", + // the region should be extracted from the zone attribute + "", + "country-region"), + gaeTestArguments( + "flex", "country-region-zone", "country-region-zone", "", "country-region"), + gaeTestArguments("", "country-region-zone", "country-region-zone", "", "country-region")); + } +} diff --git a/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/TestUtils.java b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/TestUtils.java new file mode 100644 index 000000000..6bf951f56 --- /dev/null +++ b/gcp-resources/src/test/java/io/opentelemetry/contrib/gcp/resource/TestUtils.java @@ -0,0 +1,23 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.contrib.gcp.resource; + +import static com.github.tomakehurst.wiremock.client.WireMock.aResponse; +import static com.github.tomakehurst.wiremock.client.WireMock.get; +import static com.github.tomakehurst.wiremock.client.WireMock.stubFor; +import static com.github.tomakehurst.wiremock.client.WireMock.urlEqualTo; + +final class TestUtils { + private TestUtils() {} + + // Helper method to help stub endpoints + static void stubEndpoint(String endpointPath, String responseBody) { + stubFor( + get(urlEqualTo(endpointPath)) + .willReturn( + aResponse().withHeader("Metadata-Flavor", "Google").withBody(responseBody))); + } +}