diff --git a/README.md b/README.md index 16515506..1ff44801 100644 --- a/README.md +++ b/README.md @@ -48,4 +48,4 @@ See [Autoconfigure Readme](exporters/auto/README.md) for installation and usage [maven-image]: https://maven-badges.herokuapp.com/maven-central/com.google.cloud.opentelemetry/exporter-trace/badge.svg -[maven-url]: https://maven-badges.herokuapp.com/maven-central/com.google.cloud.opentelemetry/exporter-trace \ No newline at end of file +[maven-url]: https://maven-badges.herokuapp.com/maven-central/com.google.cloud.opentelemetry/exporter-trace diff --git a/build.gradle b/build.gradle index 1edfd04e..69edb7be 100644 --- a/build.gradle +++ b/build.gradle @@ -54,7 +54,6 @@ ext.isReleaseVersion = !version.toString().endsWith("-SNAPSHOT") subprojects { apply plugin: 'jacoco' apply plugin: 'java-library' - apply plugin: 'maven' apply plugin: 'maven-publish' apply plugin: 'signing' apply plugin: 'com.diffplug.spotless' @@ -62,8 +61,8 @@ subprojects { group = "com.google.cloud.opentelemetry" // Note: Version now comes from nebula plugin - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 repositories { mavenCentral() @@ -104,10 +103,10 @@ subprojects { ext { assertjVersion = '3.22.0' autoServiceVersion = '1.0-rc7' - autoValueVersion = '1.7.4' + autoValueVersion = '1.10.1' slf4jVersion = '1.7.30' - googleCloudVersion = '2.0.5' - googleTraceVersion = '2.0.0' + googleCloudVersion = '2.8.28' + googleTraceVersion = '2.1.12' cloudMonitoringVersion = '3.0.0' openTelemetryVersion = '1.15.0' openTelemetryInstrumentationVersion = '1.14.0' @@ -119,18 +118,22 @@ subprojects { springWebVersion = '2.4.5' springOpenFeignVersion = '3.0.0' springOtelVersion = '1.0.0-M8' + cloudEventsCoreVersion = '2.4.0' + cloudFunctionsFrameworkApiVersion = '1.0.4' libraries = [ auto_service_annotations : "com.google.auto.service:auto-service-annotations:${autoServiceVersion}", auto_service : "com.google.auto.service:auto-service:${autoServiceVersion}", auto_value_annotations : "com.google.auto.value:auto-value-annotations:${autoValueVersion}", auto_value : "com.google.auto.value:auto-value:${autoValueVersion}", + cloudevents_core : "io.cloudevents:cloudevents-core:${cloudEventsCoreVersion}", google_cloud_core : "com.google.cloud:google-cloud-core:${googleCloudVersion}", google_cloud_trace : "com.google.cloud:google-cloud-trace:${googleTraceVersion}", - google_cloud_trace_grpc : "com.google.api.grpc:grpc-google-cloud-trace-v2:2.0.1", + google_cloud_trace_grpc : "com.google.api.grpc:grpc-google-cloud-trace-v2:${googleTraceVersion}", google_cloud_monitoring : "com.google.cloud:google-cloud-monitoring:${cloudMonitoringVersion}", google_cloud_monitoring_grpc : "com.google.cloud:grpc-google-cloud-monitoring-v3:${cloudMonitoringVersion}", google_cloud_pubsub : "com.google.cloud:google-cloud-pubsub:${pubSubVersion}", + google_cloud_functions_framework : "com.google.cloud.functions:functions-framework-api:${cloudFunctionsFrameworkApiVersion}", slf4j : "org.slf4j:slf4j-api:${slf4jVersion}", opentelemetry_api : "io.opentelemetry:opentelemetry-api:${openTelemetryVersion}", opentelemetry_context : "io.opentelemetry:opentelemetry-context:${openTelemetryVersion}", diff --git a/cloudbuild-e2e-cloud-functions-gen2.yaml b/cloudbuild-e2e-cloud-functions-gen2.yaml new file mode 100644 index 00000000..3d91a20b --- /dev/null +++ b/cloudbuild-e2e-cloud-functions-gen2.yaml @@ -0,0 +1,49 @@ +# Copyright 2022 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +steps: + # Generate shadowJar for the instrumented test server + - name: "gradle:7.5.1-jdk11" + id: generate-jar + entrypoint: "gradle" + timeout: 4m + args: ["shadowJar"] + + # Zip the generated JAR file + - name: ubuntu + id: zip-jar + entrypoint: bash + args: + - '-c' + - | + apt-get update && \ + apt-get install zip --assume-yes && \ + cd e2e-test-server/build/libs && \ + zip function-source e2e-test-server-0.1.0-SNAPSHOT-all.jar + + # Run the test + - name: $_TEST_RUNNER_IMAGE + id: run-tests-cloudfunction + dir: / + env: ["PROJECT_ID=$PROJECT_ID"] + args: + - cloud-functions-gen2 + - --functionsource=/workspace/e2e-test-server/build/libs/function-source.zip + - --runtime=java11 + - --entrypoint=com.google.cloud.opentelemetry.endtoend.CloudFunctionHandler + +logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs +substitutions: + _TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.16.0 + _TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-java-e2e-test-server:${SHORT_SHA} diff --git a/cloudbuild-e2e-cloud-run.yaml b/cloudbuild-e2e-cloud-run.yaml new file mode 100644 index 00000000..495ba797 --- /dev/null +++ b/cloudbuild-e2e-cloud-run.yaml @@ -0,0 +1,37 @@ +# Copyright 2022 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +steps: + # Wait for the image to exist + - name: "docker" + id: wait-for-image + entrypoint: "sh" + timeout: 3m + env: ["_TEST_SERVER_IMAGE=${_TEST_SERVER_IMAGE}"] + args: + - e2e-test-server/wait-for-image.sh + + # Run the test + - name: $_TEST_RUNNER_IMAGE + id: run-tests-cloudrun + dir: / + env: ["PROJECT_ID=$PROJECT_ID"] + args: + - cloud-run + - --image=$_TEST_SERVER_IMAGE + +logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs +substitutions: + _TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.16.0 + _TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-java-e2e-test-server:${SHORT_SHA} diff --git a/cloudbuild-e2e-gae.yaml b/cloudbuild-e2e-gae.yaml new file mode 100644 index 00000000..a0955cc6 --- /dev/null +++ b/cloudbuild-e2e-gae.yaml @@ -0,0 +1,38 @@ +# Copyright 2022 Google +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +steps: + # Wait for the image to exist + - name: "docker" + id: wait-for-image + entrypoint: "sh" + timeout: 3m + env: [ "_TEST_SERVER_IMAGE=${_TEST_SERVER_IMAGE}" ] + args: + - e2e-test-server/wait-for-image.sh + + # Run the test + - name: $_TEST_RUNNER_IMAGE + id: run-tests-gae + dir: / + env: [ "PROJECT_ID=$PROJECT_ID" ] + args: + - gae + - --image=$_TEST_SERVER_IMAGE + - --runtime=java11 + +logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs +substitutions: + _TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.16.0 + _TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-java-e2e-test-server:${SHORT_SHA} diff --git a/cloudbuild-e2e-gce.yaml b/cloudbuild-e2e-gce.yaml index f43afc51..db0ce214 100644 --- a/cloudbuild-e2e-gce.yaml +++ b/cloudbuild-e2e-gce.yaml @@ -33,5 +33,5 @@ steps: logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs substitutions: - _TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.10.0 + _TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.16.0 _TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-java-e2e-test-server:${SHORT_SHA} diff --git a/cloudbuild-e2e-gke.yaml b/cloudbuild-e2e-gke.yaml index 6f5f6773..c0babd27 100644 --- a/cloudbuild-e2e-gke.yaml +++ b/cloudbuild-e2e-gke.yaml @@ -32,5 +32,5 @@ steps: logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs substitutions: - _TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.10.0 + _TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.16.0 _TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-java-e2e-test-server:${SHORT_SHA} diff --git a/cloudbuild-e2e-local.yaml b/cloudbuild-e2e-local.yaml index a83d20a4..124a87a7 100644 --- a/cloudbuild-e2e-local.yaml +++ b/cloudbuild-e2e-local.yaml @@ -34,5 +34,5 @@ steps: logsBucket: gs://opentelemetry-ops-e2e-cloud-build-logs substitutions: - _TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.10.0 + _TEST_RUNNER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-e2e-testing:0.16.0 _TEST_SERVER_IMAGE: gcr.io/${PROJECT_ID}/opentelemetry-operations-java-e2e-test-server:${SHORT_SHA} diff --git a/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/AttributesExtractorUtil.java b/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/AttributesExtractorUtil.java new file mode 100644 index 00000000..b4d9bd74 --- /dev/null +++ b/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/AttributesExtractorUtil.java @@ -0,0 +1,132 @@ +/* + * Copyright 2022 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.opentelemetry.detectors; + +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; + +/** + * A utility class that contains method that facilitate extraction of attributes from environment + * variables and metadata configurations. + * + *

This class only adds helper methods to extract {@link ResourceAttributes} that are common + * across all the supported compute environments. + */ +public class AttributesExtractorUtil { + + /** + * Utility method to extract cloud availability zone from passed {@link GCPMetadataConfig}. The + * method modifies the passed attributesBuilder by adding the extracted property to it. If the + * zone cannot be found, calling this method has no effect. + * + *

+ * + *

Example zone: australia-southeast1-a + * + * @param attributesBuilder The {@link AttributesBuilder} to which the extracted property needs to + * be added. + * @param metadataConfig The {@link GCPMetadataConfig} from which the cloud availability zone + * value is extracted. + */ + public static void addAvailabilityZoneFromMetadata( + AttributesBuilder attributesBuilder, GCPMetadataConfig metadataConfig) { + String zone = metadataConfig.getZone(); + if (zone != null) { + attributesBuilder.put(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, zone); + } + } + + /** + * Utility method to extract the cloud region from passed {@link GCPMetadataConfig}. The method + * modifies the passed attributesBuilder by adding the extracted property to it. + * + *

+ * + *

Example region: australia-southeast1 + * + * @param attributesBuilder The {@link AttributesBuilder} to which the extracted property needs to + * be added. + * @param metadataConfig The {@link GCPMetadataConfig} from which the cloud region value is + * extracted. + */ + public static void addCloudRegionFromMetadataUsingZone( + AttributesBuilder attributesBuilder, GCPMetadataConfig metadataConfig) { + String zone = metadataConfig.getZone(); + if (zone != null) { + // Parsing required to scope up to a region + String[] splitArr = zone.split("-"); + if (splitArr.length > 2) { + attributesBuilder.put(ResourceAttributes.CLOUD_REGION, splitArr[0] + "-" + splitArr[1]); + } + } + } + + /** + * Utility method to extract the cloud region from passed {@link GCPMetadataConfig}. The method + * modifies the passed attributesBuilder by adding the extracted property to it. + * + *

+ * + *

Example region: australia-southeast1 + * + * @param attributesBuilder The {@link AttributesBuilder} to which the extracted property needs to + * be added. + * @param metadataConfig The {@link GCPMetadataConfig} from which the cloud region value is + * extracted. + */ + public static void addCloudRegionFromMetadataUsingRegion( + AttributesBuilder attributesBuilder, GCPMetadataConfig metadataConfig) { + String region = metadataConfig.getRegion(); + if (region != null) { + attributesBuilder.put(ResourceAttributes.CLOUD_REGION, region); + } + } + + /** + * Utility method to extract the current compute instance ID from the passed {@link + * GCPMetadataConfig}. The method modifies the passed attributesBuilder by adding the extracted + * property to it. + * + *

+ * + * @param attributesBuilder The {@link AttributesBuilder} to which the extracted property needs to + * be added. + * @param metadataConfig The {@link GCPMetadataConfig} from which the instance ID value is + * extracted. + */ + public static void addInstanceIdFromMetadata( + AttributesBuilder attributesBuilder, GCPMetadataConfig metadataConfig) { + String instanceId = metadataConfig.getInstanceId(); + if (instanceId != null) { + attributesBuilder.put(ResourceAttributes.FAAS_ID, instanceId); + } + } +} diff --git a/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCEResource.java b/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCEResource.java deleted file mode 100644 index 77f42b5b..00000000 --- a/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCEResource.java +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2022 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.cloud.opentelemetry.detectors; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; - -public final class GCEResource implements ResourceProvider { - private final GCPMetadataConfig metadata; - - public GCEResource() { - this(GCPMetadataConfig.DEFAULT_INSTANCE); - } - - // For testing only - public GCEResource(GCPMetadataConfig metadataConfig) { - this.metadata = metadataConfig; - } - - public Attributes getAttributes() { - if (!metadata.isRunningOnGcp()) { - return Attributes.empty(); - } - - AttributesBuilder attrBuilders = Attributes.builder(); - attrBuilders.put(ResourceAttributes.CLOUD_PROVIDER, ResourceAttributes.CloudProviderValues.GCP); - attrBuilders.put( - ResourceAttributes.CLOUD_PLATFORM, - ResourceAttributes.CloudPlatformValues.GCP_COMPUTE_ENGINE); - - String projectId = metadata.getProjectId(); - if (projectId != null) { - attrBuilders.put(ResourceAttributes.CLOUD_ACCOUNT_ID, projectId); - } - - // Example zone: australia-southeast1-a - String zone = metadata.getZone(); - if (zone != null) { - attrBuilders.put(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, zone); - - // Parsing required to scope up to a region - String[] splitArr = zone.split("-"); - if (splitArr.length > 2) { - attrBuilders.put(ResourceAttributes.CLOUD_REGION, splitArr[0] + "-" + splitArr[1]); - } - } - - String instanceId = metadata.getInstanceId(); - if (instanceId != null) { - attrBuilders.put(ResourceAttributes.HOST_ID, instanceId); - } - - String instanceName = metadata.getInstanceName(); - if (instanceName != null) { - attrBuilders.put(ResourceAttributes.HOST_NAME, instanceName); - } - - String hostType = metadata.getMachineType(); - if (hostType != null) { - attrBuilders.put(ResourceAttributes.HOST_TYPE, hostType); - } - - return attrBuilders.build(); - } - - @Override - public Resource createResource(ConfigProperties config) { - return Resource.create(getAttributes()); - } -} diff --git a/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCPMetadataConfig.java b/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCPMetadataConfig.java index 29319279..454906cf 100644 --- a/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCPMetadataConfig.java +++ b/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCPMetadataConfig.java @@ -62,6 +62,18 @@ String getZone() { 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 + // Returns null on 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; + } + // Example response: projects/640212054955/machineTypes/e2-medium String getMachineType() { String machineType = getAttribute("instance/machine-type"); @@ -81,6 +93,11 @@ 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"); diff --git a/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCPResource.java b/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCPResource.java new file mode 100644 index 00000000..d1286e03 --- /dev/null +++ b/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GCPResource.java @@ -0,0 +1,311 @@ +/* + * Copyright 2022 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.opentelemetry.detectors; + +import io.opentelemetry.api.common.Attributes; +import io.opentelemetry.api.common.AttributesBuilder; +import io.opentelemetry.api.internal.StringUtils; +import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; +import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.sdk.resources.Resource; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import java.util.logging.Logger; + +/** + * This class is used to detect the correct GCP compute platform resource. Supports detection of + * Google Compute Engine (GCE), Google Kubernetes Engine (GKE), Google Cloud Functions (GCF), Google + * App Engine (GAE) and Google Cloud Run (GCR). + */ +public class GCPResource implements ResourceProvider { + private final GCPMetadataConfig metadata; + private final EnvVars envVars; + + private static final Logger LOGGER = Logger.getLogger(GCPResource.class.getSimpleName()); + + public GCPResource() { + this.metadata = GCPMetadataConfig.DEFAULT_INSTANCE; + this.envVars = EnvVars.DEFAULT_INSTANCE; + } + + // for testing only + GCPResource(GCPMetadataConfig metadata, EnvVars envVars) { + this.metadata = metadata; + this.envVars = envVars; + } + + /** + * Generates and returns the attributes for the resource. The attributes vary depending on the + * type of resource detected. + * + * @return The {@link Attributes} for the detected resource. + */ + public Attributes getAttributes() { + if (!metadata.isRunningOnGcp()) { + return Attributes.empty(); + } + + // This is running on some sort of GCPCompute - figure out the platform + AttributesBuilder attrBuilder = Attributes.builder(); + attrBuilder.put(ResourceAttributes.CLOUD_PROVIDER, ResourceAttributes.CloudProviderValues.GCP); + + if (!(generateGKEAttributesIfApplicable(attrBuilder) + || generateGCRAttributesIfApplicable(attrBuilder) + || generateGCFAttributesIfApplicable(attrBuilder) + || generateGAEAttributesIfApplicable(attrBuilder))) { + // none of the above GCP platforms is applicable, default to GCE + addGCEAttributes(attrBuilder); + } + + return attrBuilder.build(); + } + + @Override + public Resource createResource(ConfigProperties config) { + return Resource.create(getAttributes()); + } + + /** + * Updates the attributes builder with required attributes for GCE resource, if GCE resource is + * applicable. By default, if the resource is running on GCP, it is assumed to be GCE. This means + * additional attributes are added/overwritten if later on, the resource is identified to be some + * other platform - like GKE, GAE, etc. + */ + private void addGCEAttributes(AttributesBuilder attrBuilder) { + attrBuilder.put( + ResourceAttributes.CLOUD_PLATFORM, + ResourceAttributes.CloudPlatformValues.GCP_COMPUTE_ENGINE); + + String projectId = metadata.getProjectId(); + if (projectId != null) { + attrBuilder.put(ResourceAttributes.CLOUD_ACCOUNT_ID, projectId); + } + + AttributesExtractorUtil.addAvailabilityZoneFromMetadata(attrBuilder, metadata); + AttributesExtractorUtil.addCloudRegionFromMetadataUsingZone(attrBuilder, metadata); + + String instanceId = metadata.getInstanceId(); + if (instanceId != null) { + attrBuilder.put(ResourceAttributes.HOST_ID, instanceId); + } + + String instanceName = metadata.getInstanceName(); + if (instanceName != null) { + attrBuilder.put(ResourceAttributes.HOST_NAME, instanceName); + } + + String hostType = metadata.getMachineType(); + if (hostType != null) { + attrBuilder.put(ResourceAttributes.HOST_TYPE, hostType); + } + } + + /** + * Updates the attributes with the required keys for a GKE (Google Kubernetes Engine) environment. + * The attributes are not updated in case the environment is not deemed to be GKE. + * + * @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the + * necessary keys. + * @return a boolean indicating if the environment was determined to be GKE and GKE specific + * attributes were applied. + */ + private boolean generateGKEAttributesIfApplicable(AttributesBuilder attrBuilder) { + if (envVars.get("KUBERNETES_SERVICE_HOST") != null) { + attrBuilder.put( + ResourceAttributes.CLOUD_PLATFORM, + ResourceAttributes.CloudPlatformValues.GCP_KUBERNETES_ENGINE); + String podName = envVars.get("POD_NAME"); + if (podName != null && !podName.isEmpty()) { + attrBuilder.put(ResourceAttributes.K8S_POD_NAME, podName); + } else { + // If nothing else is set, at least use hostname for pod name. + attrBuilder.put(ResourceAttributes.K8S_POD_NAME, envVars.get("HOSTNAME")); + } + + String namespace = envVars.get("NAMESPACE"); + if (namespace != null && !namespace.isEmpty()) { + attrBuilder.put(ResourceAttributes.K8S_NAMESPACE_NAME, namespace); + } + + String containerName = envVars.get("CONTAINER_NAME"); + if (containerName != null && !containerName.isEmpty()) { + attrBuilder.put(ResourceAttributes.K8S_CONTAINER_NAME, containerName); + } + + String instanceId = metadata.getInstanceId(); + if (instanceId != null) { + attrBuilder.put(ResourceAttributes.HOST_ID, instanceId); + } + + String clusterLocation = metadata.getClusterLocation(); + assignGKEAvailabilityZoneOrRegion(clusterLocation, attrBuilder); + + String clusterName = metadata.getClusterName(); + if (clusterName != null && !clusterName.isEmpty()) { + attrBuilder.put(ResourceAttributes.K8S_CLUSTER_NAME, clusterName); + } + return true; + } + return false; + } + + /** + * Function that assigns either the cloud region or cloud availability zone depending on whether + * the cluster is regional or zonal respectively. Assigns both values if the cluster location + * passed is in an unexpected format. + * + * @param clusterLocation The location of the GKE cluster. Can either be an availability zone or a + * region. + * @param attributesBuilder The {@link AttributesBuilder} object that needs to be updated with the + * necessary keys. + */ + private void assignGKEAvailabilityZoneOrRegion( + String clusterLocation, AttributesBuilder attributesBuilder) { + long dashCount = + StringUtils.isNullOrEmpty(clusterLocation) + ? 0 + : clusterLocation.chars().filter(ch -> ch == '-').count(); + switch ((int) dashCount) { + case 1: + // this is a region + attributesBuilder.put(ResourceAttributes.CLOUD_REGION, clusterLocation); + break; + case 2: + // this is a zone + attributesBuilder.put(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, clusterLocation); + break; + default: + // TODO: Figure out how to handle unexpected conditions like this - Issue #183 + LOGGER.severe( + String.format("Unrecognized format for cluster location: %s", clusterLocation)); + } + } + + /** + * Updates the attributes with the required keys for a GCR (Google Cloud Run) environment. The + * attributes are not updated in case the environment is not deemed to be GCR. + * + * @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the + * necessary keys. + * @return a boolean indicating if the environment was determined to be GCR and GCR specific + * attributes were applied. + */ + private boolean generateGCRAttributesIfApplicable(AttributesBuilder attrBuilder) { + if (envVars.get("K_CONFIGURATION") != null && envVars.get("FUNCTION_TARGET") == null) { + // add the resource attributes for Cloud Run + attrBuilder.put( + ResourceAttributes.CLOUD_PLATFORM, ResourceAttributes.CloudPlatformValues.GCP_CLOUD_RUN); + + updateCommonAttributesForServerlessCompute(attrBuilder); + return true; + } + return false; + } + + /** + * Updates the attributes with the required keys for a GCF (Google Cloud Functions) environment. + * The attributes are not updated in case the environment is not deemed to be GCF. + * + * @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the + * necessary keys. + * @return a boolean indicating if the environment was determined to be GCF and GCF specific + * attributes were applied. + */ + private boolean generateGCFAttributesIfApplicable(AttributesBuilder attrBuilder) { + if (envVars.get("FUNCTION_TARGET") != null) { + // add the resource attributes for Cloud Function + attrBuilder.put( + ResourceAttributes.CLOUD_PLATFORM, + ResourceAttributes.CloudPlatformValues.GCP_CLOUD_FUNCTIONS); + + updateCommonAttributesForServerlessCompute(attrBuilder); + return true; + } + return false; + } + + /** + * This function adds common attributes required for most serverless compute platforms within GCP. + * Currently, these attributes are required for both GCF and GCR. + * + * @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the + * necessary keys. + */ + private void updateCommonAttributesForServerlessCompute(AttributesBuilder attrBuilder) { + String serverlessComputeName = envVars.get("K_SERVICE"); + if (serverlessComputeName != null) { + attrBuilder.put(ResourceAttributes.FAAS_NAME, serverlessComputeName); + } + + String serverlessComputeVersion = envVars.get("K_REVISION"); + if (serverlessComputeVersion != null) { + attrBuilder.put(ResourceAttributes.FAAS_VERSION, serverlessComputeVersion); + } + + AttributesExtractorUtil.addAvailabilityZoneFromMetadata(attrBuilder, metadata); + AttributesExtractorUtil.addCloudRegionFromMetadataUsingZone(attrBuilder, metadata); + AttributesExtractorUtil.addInstanceIdFromMetadata(attrBuilder, metadata); + } + + /** + * Updates the attributes with the required keys for a GAE (Google App Engine) environment. The + * attributes are not updated in case the environment is not deemed to be GAE. + * + * @param attrBuilder The {@link AttributesBuilder} object that needs to be updated with the + * necessary keys. + * @return a boolean indicating if the environment was determined to be GAE and GAE specific + * attributes were applied. + */ + private boolean generateGAEAttributesIfApplicable(AttributesBuilder attrBuilder) { + if (envVars.get("GAE_SERVICE") != null) { + // add the resource attributes for App Engine + attrBuilder.put( + ResourceAttributes.CLOUD_PLATFORM, ResourceAttributes.CloudPlatformValues.GCP_APP_ENGINE); + + String appModuleName = envVars.get("GAE_SERVICE"); + if (appModuleName != null) { + attrBuilder.put(ResourceAttributes.FAAS_NAME, appModuleName); + } + + String appVersionId = envVars.get("GAE_VERSION"); + if (appVersionId != null) { + attrBuilder.put(ResourceAttributes.FAAS_VERSION, appVersionId); + } + + String appInstanceId = envVars.get("GAE_INSTANCE"); + if (appInstanceId != null) { + attrBuilder.put(ResourceAttributes.FAAS_ID, appInstanceId); + } + updateAttributesWithRegion(attrBuilder); + AttributesExtractorUtil.addAvailabilityZoneFromMetadata(attrBuilder, metadata); + return true; + } + return false; + } + + /** + * Selects the correct method to extract the region, depending on the GAE environment. + * + * @param attributesBuilder The {@link AttributesBuilder} object to which the extracted region + * would be added. + */ + private void updateAttributesWithRegion(AttributesBuilder attributesBuilder) { + if (envVars.get("GAE_ENV") != null && envVars.get("GAE_ENV").equals("standard")) { + AttributesExtractorUtil.addCloudRegionFromMetadataUsingRegion(attributesBuilder, metadata); + } else { + AttributesExtractorUtil.addCloudRegionFromMetadataUsingZone(attributesBuilder, metadata); + } + } +} diff --git a/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GKEResource.java b/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GKEResource.java deleted file mode 100644 index cfd369d2..00000000 --- a/detectors/resources/src/main/java/com/google/cloud/opentelemetry/detectors/GKEResource.java +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2022 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.cloud.opentelemetry.detectors; - -import io.opentelemetry.api.common.Attributes; -import io.opentelemetry.api.common.AttributesBuilder; -import io.opentelemetry.sdk.autoconfigure.spi.ConfigProperties; -import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; -import io.opentelemetry.sdk.resources.Resource; -import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; - -public final class GKEResource implements ResourceProvider { - private final GCPMetadataConfig metadata; - private final EnvVars envVars; - - public GKEResource() { - this.metadata = GCPMetadataConfig.DEFAULT_INSTANCE; - this.envVars = EnvVars.DEFAULT_INSTANCE; - } - - // For testing only - public GKEResource(GCPMetadataConfig metadataConfig, EnvVars envVars) { - this.metadata = metadataConfig; - this.envVars = envVars; - } - - public Attributes getAttributes() { - GCEResource gce = new GCEResource(this.metadata); - - Attributes gceAttributes = gce.getAttributes(); - - if (envVars.get("KUBERNETES_SERVICE_HOST") == null) { - return gceAttributes; - } - - AttributesBuilder attrBuilders = Attributes.builder(); - attrBuilders.putAll(gceAttributes); - - attrBuilders.put( - ResourceAttributes.CLOUD_PLATFORM, - ResourceAttributes.CloudPlatformValues.GCP_KUBERNETES_ENGINE); - String podName = envVars.get("POD_NAME"); - if (podName != null && !podName.isEmpty()) { - attrBuilders.put(ResourceAttributes.K8S_POD_NAME, podName); - } else { - // If nothing else is set, at least use hostname for pod name. - attrBuilders.put(ResourceAttributes.K8S_POD_NAME, envVars.get("HOSTNAME")); - } - - String namespace = envVars.get("NAMESPACE"); - if (namespace != null && !namespace.isEmpty()) { - attrBuilders.put(ResourceAttributes.K8S_NAMESPACE_NAME, namespace); - } - - String containerName = envVars.get("CONTAINER_NAME"); - if (containerName != null && !containerName.isEmpty()) { - attrBuilders.put(ResourceAttributes.K8S_CONTAINER_NAME, containerName); - } - - String clusterName = metadata.getClusterName(); - if (clusterName != null && !clusterName.isEmpty()) { - attrBuilders.put(ResourceAttributes.K8S_CLUSTER_NAME, clusterName); - } - - return attrBuilders.build(); - } - - @Override - public Resource createResource(ConfigProperties config) { - return Resource.create(getAttributes()); - } -} diff --git a/detectors/resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider b/detectors/resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider index 16189b37..a5d52a5c 100644 --- a/detectors/resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider +++ b/detectors/resources/src/main/resources/META-INF/services/io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider @@ -1,2 +1 @@ -com.google.cloud.opentelemetry.detectors.GCEResource -com.google.cloud.opentelemetry.detectors.GKEResource +com.google.cloud.opentelemetry.detectors.GCPResource diff --git a/detectors/resources/src/test/java/com/google/cloud/opentelemetry/detectors/GCEResourceTest.java b/detectors/resources/src/test/java/com/google/cloud/opentelemetry/detectors/GCEResourceTest.java deleted file mode 100644 index b284c38d..00000000 --- a/detectors/resources/src/test/java/com/google/cloud/opentelemetry/detectors/GCEResourceTest.java +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2022 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.cloud.opentelemetry.detectors; - -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.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.junit.Assert.assertTrue; - -import com.github.tomakehurst.wiremock.junit.WireMockRule; -import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; -import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; -import java.util.ServiceLoader; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class GCEResourceTest { - @Rule public final WireMockRule wireMockRule = new WireMockRule(8089); - private final GCPMetadataConfig metadataConfig = new GCPMetadataConfig("http://localhost:8089/"); - private final GCEResource testResource = new GCEResource(metadataConfig); - - // Helper method to help stub endpoints - private void stubEndpoint(String endpointPath, String responseBody) { - stubFor( - get(urlEqualTo(endpointPath)) - .willReturn( - aResponse().withHeader("Metadata-Flavor", "Google").withBody(responseBody))); - } - - @Test - public void findsWithServiceLoader() { - ServiceLoader services = - ServiceLoader.load(ResourceProvider.class, getClass().getClassLoader()); - assertTrue( - "Could not load GCE Resource detector using serviceloader, found: " + services, - services.stream().anyMatch(provider -> provider.type().equals(GCEResource.class))); - } - - @Test - public void testGCEResourceNotGCP() { - GCEResource test = new GCEResource(); - - /* The default meta data url is unreachable through testing so getAttributes should not detect a - GCP environment, hence returning empty attributes */ - assertThat(test.getAttributes()).isEmpty(); - } - - @Test - public void testGCEResourceWithGCEAttributesSucceeds() { - stubEndpoint("/project/project-id", "GCE-pid"); - stubEndpoint("/instance/zone", "country-region-zone"); - stubEndpoint("/instance/id", "GCE-instance-id"); - stubEndpoint("/instance/name", "GCE-instance-name"); - stubEndpoint("/instance/machine-type", "GCE-instance-type"); - - assertThat(testResource.getAttributes()) - .hasSize(8) - .containsEntry( - ResourceAttributes.CLOUD_PROVIDER, ResourceAttributes.CloudProviderValues.GCP) - .containsEntry( - ResourceAttributes.CLOUD_PLATFORM, - ResourceAttributes.CloudPlatformValues.GCP_COMPUTE_ENGINE) - .containsEntry(ResourceAttributes.CLOUD_ACCOUNT_ID, "GCE-pid") - .containsEntry(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, "country-region-zone") - .containsEntry(ResourceAttributes.CLOUD_REGION, "country-region") - .containsEntry(ResourceAttributes.HOST_ID, "GCE-instance-id") - .containsEntry(ResourceAttributes.HOST_NAME, "GCE-instance-name") - .containsEntry(ResourceAttributes.HOST_TYPE, "GCE-instance-type"); - } - - @Test - public void testGCEResourceNonGCPEndpoint() { - stubFor( - get(urlEqualTo("/project/project-id")) - .willReturn(aResponse().withBody("nonGCPendpointTest"))); - assertThat(testResource.getAttributes()).isEmpty(); - } -} diff --git a/detectors/resources/src/test/java/com/google/cloud/opentelemetry/detectors/GCPResourceTest.java b/detectors/resources/src/test/java/com/google/cloud/opentelemetry/detectors/GCPResourceTest.java new file mode 100644 index 00000000..4075dd7c --- /dev/null +++ b/detectors/resources/src/test/java/com/google/cloud/opentelemetry/detectors/GCPResourceTest.java @@ -0,0 +1,252 @@ +/* + * Copyright 2022 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.opentelemetry.detectors; + +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.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; +import static org.junit.Assert.assertTrue; + +import com.github.tomakehurst.wiremock.junit.WireMockRule; +import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; +import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; +import java.util.HashMap; +import java.util.Map; +import java.util.ServiceLoader; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.JUnit4; + +@RunWith(JUnit4.class) +public class GCPResourceTest { + @Rule public final WireMockRule wireMockRule = new WireMockRule(8089); + private final GCPMetadataConfig metadataConfig = new GCPMetadataConfig("http://localhost:8089/"); + private static final Map envVars = new HashMap<>(); + + @Before + public void clearEnvVars() { + envVars.clear(); + } + + @Test + public void findsWithServiceLoader() { + ServiceLoader services = + ServiceLoader.load(ResourceProvider.class, getClass().getClassLoader()); + assertTrue( + "Could not load GCP Resource detector using serviceloader, found: " + services, + services.stream().anyMatch(provider -> provider.type().equals(GCPResource.class))); + } + + @Test + public void testGCPComputeResourceNotGCP() { + GCPResource testResource = new GCPResource(); + + // The default metadata url is unreachable through testing so getAttributes should not detect a + // GCP environment, hence returning empty attributes. + assertThat(testResource.getAttributes()).isEmpty(); + } + + @Test + public void testGCPComputeResourceNonGCPEndpoint() { + // intentionally not providing the required Metadata-Flovor header with the + // request to mimic non GCP endpoint + stubFor( + get(urlEqualTo("/project/project-id")) + .willReturn(aResponse().withBody("nonGCPendpointTest"))); + GCPResource testResource = new GCPResource(metadataConfig, new EnvVarMock(envVars)); + assertThat(testResource.getAttributes()).isEmpty(); + } + + /** Google Compute Engine Tests * */ + @Test + public void testGCEResourceWithGCEAttributesSucceeds() { + stubEndpoint("/project/project-id", "GCE-pid"); + stubEndpoint("/instance/zone", "country-region-zone"); + stubEndpoint("/instance/id", "GCE-instance-id"); + stubEndpoint("/instance/name", "GCE-instance-name"); + stubEndpoint("/instance/machine-type", "GCE-instance-type"); + + final GCPResource testResource = new GCPResource(metadataConfig, new EnvVarMock(envVars)); + assertThat(testResource.getAttributes()) + .hasSize(8) + .containsEntry( + ResourceAttributes.CLOUD_PROVIDER, ResourceAttributes.CloudProviderValues.GCP) + .containsEntry( + ResourceAttributes.CLOUD_PLATFORM, + ResourceAttributes.CloudPlatformValues.GCP_COMPUTE_ENGINE) + .containsEntry(ResourceAttributes.CLOUD_ACCOUNT_ID, "GCE-pid") + .containsEntry(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, "country-region-zone") + .containsEntry(ResourceAttributes.CLOUD_REGION, "country-region") + .containsEntry(ResourceAttributes.HOST_ID, "GCE-instance-id") + .containsEntry(ResourceAttributes.HOST_NAME, "GCE-instance-name") + .containsEntry(ResourceAttributes.HOST_TYPE, "GCE-instance-type"); + } + + /** Google Kubernetes Engine Tests * */ + @Test + public 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"); + + stubEndpoint("/project/project-id", "GCE-pid"); + stubEndpoint("/instance/id", "GCE-instance-id"); + stubEndpoint("/instance/name", "GCE-instance-name"); + stubEndpoint("/instance/machine-type", "GCE-instance-type"); + stubEndpoint("/instance/attributes/cluster-name", "GKE-cluster-name"); + stubEndpoint("/instance/attributes/cluster-location", "country-region-zone"); + + GCPResource testResource = new GCPResource(metadataConfig, new EnvVarMock(envVars)); + assertThat(testResource.getAttributes()) + .hasSize(8) + .containsEntry(ResourceAttributes.CLOUD_PROVIDER, "gcp") + .containsEntry(ResourceAttributes.CLOUD_PLATFORM, "gcp_kubernetes_engine") + .containsEntry(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, "country-region-zone") + .containsEntry(ResourceAttributes.HOST_ID, "GCE-instance-id") + .containsEntry(ResourceAttributes.K8S_CLUSTER_NAME, "GKE-cluster-name") + .containsEntry(ResourceAttributes.K8S_NAMESPACE_NAME, "GKE-testNameSpace") + .containsEntry(ResourceAttributes.K8S_POD_NAME, "GKE-testHostName-full-1234") + .containsEntry(ResourceAttributes.K8S_CONTAINER_NAME, "GKE-testContainerName"); + } + + @Test + public 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"); + + stubEndpoint("/project/project-id", "GCE-pid"); + stubEndpoint("/instance/id", "GCE-instance-id"); + stubEndpoint("/instance/name", "GCE-instance-name"); + stubEndpoint("/instance/machine-type", "GCE-instance-type"); + stubEndpoint("/instance/attributes/cluster-name", "GKE-cluster-name"); + stubEndpoint("/instance/attributes/cluster-location", "country-region"); + + GCPResource testResource = new GCPResource(metadataConfig, new EnvVarMock(envVars)); + assertThat(testResource.getAttributes()) + .hasSize(8) + .containsEntry(ResourceAttributes.CLOUD_PROVIDER, "gcp") + .containsEntry(ResourceAttributes.CLOUD_PLATFORM, "gcp_kubernetes_engine") + .containsEntry(ResourceAttributes.CLOUD_REGION, "country-region") + .containsEntry(ResourceAttributes.HOST_ID, "GCE-instance-id") + .containsEntry(ResourceAttributes.K8S_CLUSTER_NAME, "GKE-cluster-name") + .containsEntry(ResourceAttributes.K8S_NAMESPACE_NAME, "GKE-testNameSpace") + .containsEntry(ResourceAttributes.K8S_POD_NAME, "GKE-testHostName-full-1234") + .containsEntry(ResourceAttributes.K8S_CONTAINER_NAME, "GKE-testContainerName"); + } + + /** Google Cloud Functions Tests * */ + @Test + public 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"); + + stubEndpoint("/project/project-id", "GCF-pid"); + stubEndpoint("/instance/zone", "country-region-zone"); + stubEndpoint("/instance/id", "GCF-instance-id"); + + GCPResource testResource = new GCPResource(metadataConfig, new EnvVarMock(envVars)); + assertThat(testResource.getAttributes()) + .hasSize(7) + .containsEntry( + ResourceAttributes.CLOUD_PROVIDER, ResourceAttributes.CloudProviderValues.GCP) + .containsEntry( + ResourceAttributes.CLOUD_PLATFORM, + ResourceAttributes.CloudPlatformValues.GCP_CLOUD_FUNCTIONS) + .containsEntry(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, "country-region-zone") + .containsEntry(ResourceAttributes.CLOUD_REGION, "country-region") + .containsEntry(ResourceAttributes.FAAS_NAME, envVars.get("K_SERVICE")) + .containsEntry(ResourceAttributes.FAAS_VERSION, envVars.get("K_REVISION")) + .containsEntry(ResourceAttributes.FAAS_ID, "GCF-instance-id"); + } + + /** Google App Engine Tests * */ + @Test + public void testGAEResourceWithAppEngineAttributesSucceedsInFlex() { + envVars.put("GAE_SERVICE", "app-engine-hello"); + envVars.put("GAE_VERSION", "app-engine-hello-v1"); + envVars.put("GAE_INSTANCE", "app-engine-hello-f236d"); + + stubEndpoint("/project/project-id", "GAE-pid-flex"); + // for flex, the region should be parsed from zone attribute + stubEndpoint("/instance/zone", "country-region-zone"); + stubEndpoint("/instance/region", "country-region1"); + stubEndpoint("/instance/id", "GAE-instance-id"); + + GCPResource testResource = new GCPResource(metadataConfig, new EnvVarMock(envVars)); + assertThat(testResource.getAttributes()) + .hasSize(7) + .containsEntry( + ResourceAttributes.CLOUD_PROVIDER, ResourceAttributes.CloudProviderValues.GCP) + .containsEntry( + ResourceAttributes.CLOUD_PLATFORM, + ResourceAttributes.CloudPlatformValues.GCP_APP_ENGINE) + .containsEntry(ResourceAttributes.CLOUD_REGION, "country-region") + .containsEntry(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, "country-region-zone") + .containsEntry(ResourceAttributes.FAAS_NAME, envVars.get("GAE_SERVICE")) + .containsEntry(ResourceAttributes.FAAS_VERSION, envVars.get("GAE_VERSION")) + .containsEntry(ResourceAttributes.FAAS_ID, envVars.get("GAE_INSTANCE")); + } + + @Test + public void testGAEResourceWithAppEngineAttributesSucceedsInStandard() { + envVars.put("GAE_SERVICE", "app-engine-hello"); + envVars.put("GAE_VERSION", "app-engine-hello-v1"); + envVars.put("GAE_INSTANCE", "app-engine-hello-f236d"); + + stubEndpoint("/project/project-id", "GAE-pid-standard"); + // for standard, the region should be extracted from region attribute + stubEndpoint("/instance/zone", "country-region-zone"); + stubEndpoint("/instance/region", "country-region1"); + stubEndpoint("/instance/id", "GAE-instance-id"); + + Map updatedEnvVars = new HashMap<>(envVars); + updatedEnvVars.put("GAE_ENV", "standard"); + GCPResource testResource = new GCPResource(metadataConfig, new EnvVarMock(updatedEnvVars)); + assertThat(testResource.getAttributes()) + .hasSize(7) + .containsEntry( + ResourceAttributes.CLOUD_PROVIDER, ResourceAttributes.CloudProviderValues.GCP) + .containsEntry( + ResourceAttributes.CLOUD_PLATFORM, + ResourceAttributes.CloudPlatformValues.GCP_APP_ENGINE) + .containsEntry(ResourceAttributes.CLOUD_REGION, "country-region1") + .containsEntry(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, "country-region-zone") + .containsEntry(ResourceAttributes.FAAS_NAME, envVars.get("GAE_SERVICE")) + .containsEntry(ResourceAttributes.FAAS_VERSION, envVars.get("GAE_VERSION")) + .containsEntry(ResourceAttributes.FAAS_ID, envVars.get("GAE_INSTANCE")); + } + + // Helper method to help stub endpoints + private void stubEndpoint(String endpointPath, String responseBody) { + stubFor( + get(urlEqualTo(endpointPath)) + .willReturn( + aResponse().withHeader("Metadata-Flavor", "Google").withBody(responseBody))); + } +} diff --git a/detectors/resources/src/test/java/com/google/cloud/opentelemetry/detectors/GKEResourceTest.java b/detectors/resources/src/test/java/com/google/cloud/opentelemetry/detectors/GKEResourceTest.java deleted file mode 100644 index 6dd7a841..00000000 --- a/detectors/resources/src/test/java/com/google/cloud/opentelemetry/detectors/GKEResourceTest.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright 2022 Google - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.cloud.opentelemetry.detectors; - -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.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; -import static org.junit.Assert.assertTrue; - -import com.github.tomakehurst.wiremock.junit.WireMockRule; -import io.opentelemetry.sdk.autoconfigure.spi.ResourceProvider; -import io.opentelemetry.semconv.resource.attributes.ResourceAttributes; -import java.util.HashMap; -import java.util.Map; -import java.util.ServiceLoader; -import org.junit.Rule; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.junit.runners.JUnit4; - -@RunWith(JUnit4.class) -public class GKEResourceTest { - @Rule public final WireMockRule wireMockRule = new WireMockRule(8089); - private final GCPMetadataConfig metadataConfig = new GCPMetadataConfig("http://localhost:8089/"); - - // Helper method to help stub endpoints - private void stubEndpoint(String endpointPath, String responseBody) { - stubFor( - get(urlEqualTo(endpointPath)) - .willReturn( - aResponse().withHeader("Metadata-Flavor", "Google").withBody(responseBody))); - } - - @Test - public void findsWithServiceLoader() { - ServiceLoader services = - ServiceLoader.load(ResourceProvider.class, getClass().getClassLoader()); - assertTrue( - "Could not load GKE Resource detector using serviceloader, found: " + services, - services.stream().anyMatch(provider -> provider.type().equals(GKEResource.class))); - } - - @Test - public void testGKEResourceNotGCP() { - GKEResource test = new GKEResource(); - - /* The default meta data url is unreachable through testing so getAttributes should not detect a - GCP environment, hence returning empty attributes */ - // Note: Currently this does not support GKE outside of Google Cloud (e.g. GKE on AWS, GKE on - // premise) - assertThat(test.getAttributes()).isEmpty(); - } - - @Test - public void testGKEResourceWithOutDownwardAPI() { - stubEndpoint("/project/project-id", "GCE-pid"); - stubEndpoint("/instance/zone", "country-region-zone"); - stubEndpoint("/instance/id", "GCE-instance-id"); - stubEndpoint("/instance/name", "GCE-instance-name"); - stubEndpoint("/instance/machine-type", "GCE-instance-type"); - stubEndpoint("/instance/attributes/cluster-name", "GKE-cluster-name"); - Map map = new HashMap<>(); - map.put("KUBERNETES_SERVICE_HOST", "GKE-testHost"); - map.put("HOSTNAME", "GKE-testHostName"); - GKEResource testResource = new GKEResource(metadataConfig, new EnvVarMock(map)); - assertThat(testResource.getAttributes()) - .hasSize(10) - .containsEntry(ResourceAttributes.CLOUD_PROVIDER, "gcp") - .containsEntry(ResourceAttributes.CLOUD_PLATFORM, "gcp_kubernetes_engine") - .containsEntry(ResourceAttributes.CLOUD_ACCOUNT_ID, "GCE-pid") - .containsEntry(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, "country-region-zone") - .containsEntry(ResourceAttributes.CLOUD_REGION, "country-region") - .containsEntry(ResourceAttributes.HOST_ID, "GCE-instance-id") - .containsEntry(ResourceAttributes.HOST_NAME, "GCE-instance-name") - .containsEntry(ResourceAttributes.HOST_TYPE, "GCE-instance-type") - .containsEntry(ResourceAttributes.K8S_CLUSTER_NAME, "GKE-cluster-name") - .containsEntry(ResourceAttributes.K8S_POD_NAME, "GKE-testHostName"); - } - - @Test - public void testGKEResourceWithGKEAttributesSucceeds() { - stubEndpoint("/project/project-id", "GCE-pid"); - stubEndpoint("/instance/zone", "country-region-zone"); - stubEndpoint("/instance/id", "GCE-instance-id"); - stubEndpoint("/instance/name", "GCE-instance-name"); - stubEndpoint("/instance/machine-type", "GCE-instance-type"); - stubEndpoint("/instance/attributes/cluster-name", "GKE-cluster-name"); - - Map map = new HashMap<>(); - map.put("KUBERNETES_SERVICE_HOST", "GKE-testHost"); - map.put("NAMESPACE", "GKE-testNameSpace"); - // Hostname can truncate pod name, so we test downward API override. - map.put("HOSTNAME", "GKE-testHostName"); - map.put("POD_NAME", "GKE-testHostName-full-1234"); - map.put("CONTAINER_NAME", "GKE-testContainerName"); - - GKEResource testResource = new GKEResource(metadataConfig, new EnvVarMock(map)); - assertThat(testResource.getAttributes()) - .hasSize(12) - .containsEntry(ResourceAttributes.CLOUD_PROVIDER, "gcp") - .containsEntry(ResourceAttributes.CLOUD_PLATFORM, "gcp_kubernetes_engine") - .containsEntry(ResourceAttributes.CLOUD_ACCOUNT_ID, "GCE-pid") - .containsEntry(ResourceAttributes.CLOUD_AVAILABILITY_ZONE, "country-region-zone") - .containsEntry(ResourceAttributes.CLOUD_REGION, "country-region") - .containsEntry(ResourceAttributes.HOST_ID, "GCE-instance-id") - .containsEntry(ResourceAttributes.HOST_NAME, "GCE-instance-name") - .containsEntry(ResourceAttributes.HOST_TYPE, "GCE-instance-type") - .containsEntry(ResourceAttributes.K8S_CLUSTER_NAME, "GKE-cluster-name") - .containsEntry(ResourceAttributes.K8S_NAMESPACE_NAME, "GKE-testNameSpace") - .containsEntry(ResourceAttributes.K8S_POD_NAME, "GKE-testHostName-full-1234") - .containsEntry(ResourceAttributes.K8S_CONTAINER_NAME, "GKE-testContainerName"); - } -} diff --git a/e2e-test-server/build.gradle b/e2e-test-server/build.gradle index 5eddf999..4058be5b 100644 --- a/e2e-test-server/build.gradle +++ b/e2e-test-server/build.gradle @@ -20,19 +20,25 @@ plugins { } application { - mainClassName = 'com.google.cloud.opentelemetry.endtoend.Server' + mainClass.set('com.google.cloud.opentelemetry.endtoend.Server') } description = 'End-To-End integration testing server' +shadowJar { + mergeServiceFiles() +} + dependencies { - compile(libraries.opentelemetry_autoconfigure) - compile(libraries.opentelemetry_api) - compile(libraries.opentelemetry_sdk) - compile(libraries.google_cloud_trace) - compile(libraries.google_cloud_pubsub) - compile project(':exporter-trace') - compile project(':propagators-gcp') + implementation(libraries.opentelemetry_autoconfigure) + implementation(libraries.opentelemetry_api) + implementation(libraries.opentelemetry_sdk) + implementation(libraries.google_cloud_trace) + implementation(libraries.google_cloud_pubsub) + implementation(libraries.google_cloud_functions_framework) + implementation(libraries.cloudevents_core) + implementation project(':exporter-trace') + implementation project(':propagators-gcp') runtimeOnly project(':detector-resources') } diff --git a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/CloudFunctionHandler.java b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/CloudFunctionHandler.java new file mode 100644 index 00000000..ecc4324c --- /dev/null +++ b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/CloudFunctionHandler.java @@ -0,0 +1,66 @@ +/* + * Copyright 2022 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.opentelemetry.endtoend; + +import com.google.cloud.functions.CloudEventsFunction; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.pubsub.v1.PubsubMessage; +import io.cloudevents.CloudEvent; +import java.nio.charset.StandardCharsets; +import java.util.Objects; + +/** + * This class serves as an entrypoint for Google Cloud Function entrypoint. This entrypoint works + * for 2nd gen functions that have event-based triggers. + */ +public class CloudFunctionHandler implements CloudEventsFunction { + + /** + * Called to service an incoming event. This interface is implemented by user code to provide the + * action for a given background function. If this method throws any exception (including any + * {@link Error}) then the HTTP response will have a 500 status code. + * + * @param event the event. + */ + @Override + public void accept(CloudEvent event) { + // The Pub/Sub message is passed as the CloudEvent's data payload. + if (event.getData() != null) { + PubsubMessage message = getDecodedMessage(event); + Server server; + try { + server = new Server(); + server.handlePubSubMessage(message); + } catch (Exception e) { + e.printStackTrace(); + } + } + } + + private PubsubMessage getDecodedMessage(CloudEvent event) { + String cloudEventData = + new String(Objects.requireNonNull(event.getData()).toBytes(), StandardCharsets.UTF_8); + Gson gson = new Gson(); + JsonElement jsonRoot = JsonParser.parseString(cloudEventData); + System.out.println("Parsed JSON is " + jsonRoot.toString()); + String msgStr = jsonRoot.getAsJsonObject().get("message").toString(); + System.out.println("Message String is " + msgStr); + PubSubPushServer.Message message = gson.fromJson(msgStr, PubSubPushServer.Message.class); + return PubsubMessage.newBuilder().putAllAttributes(message.getAttributes()).build(); + } +} diff --git a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/Constants.java b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/Constants.java index f5b734d6..1b7ce2b5 100644 --- a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/Constants.java +++ b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/Constants.java @@ -21,19 +21,22 @@ /** Constants we use in this test. Note: Some are pulled from the env. */ public class Constants { - public static String INSTRUMENTING_MODULE_NAME = "opentelemetry-ops-e2e-test-server"; - public static String SCENARIO = "scenario"; - public static String STATUS_CODE = "status_code"; - public static String TEST_ID = "test_id"; - public static String TRACE_ID = "trace_id"; + public static final String INSTRUMENTING_MODULE_NAME = "opentelemetry-ops-e2e-test-server"; + public static final String SCENARIO = "scenario"; + public static final String STATUS_CODE = "status_code"; + public static final String TEST_ID = "test_id"; + public static final String TRACE_ID = "trace_id"; + public static final String SUBSCRIPTION_MODE_PUSH = "push"; + public static final String SUBSCRIPTION_MODE_PULL = "pull"; // TODO: Add good error messages below. - public static String SUBCRIPTION_MODE = System.getenv().getOrDefault("SUBSCRIPTION_MODE", ""); + public static String SUBSCRIPTION_MODE = System.getenv().getOrDefault("SUBSCRIPTION_MODE", ""); public static String PROJECT_ID = System.getenv().getOrDefault("PROJECT_ID", ""); public static String REQUEST_SUBSCRIPTION_NAME = System.getenv().getOrDefault("REQUEST_SUBSCRIPTION_NAME", ""); public static String RESPONSE_TOPIC_NAME = System.getenv().getOrDefault("RESPONSE_TOPIC_NAME", ""); + public static String PUSH_PORT = System.getenv().getOrDefault("PUSH_PORT", ""); public static ProjectSubscriptionName getRequestSubscription() { return ProjectSubscriptionName.of(PROJECT_ID, REQUEST_SUBSCRIPTION_NAME); diff --git a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubMessageHandler.java b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubMessageHandler.java new file mode 100644 index 00000000..03f36e77 --- /dev/null +++ b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubMessageHandler.java @@ -0,0 +1,73 @@ +/* + * Copyright 2022 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.opentelemetry.endtoend; + +import com.google.pubsub.v1.PubsubMessage; + +/** + * An interface containing functionality to handle an incoming {@link PubsubMessage} and returning + * appropriate response indicating if handling was successful or not. + */ +public interface PubSubMessageHandler { + + /** + * Represents the possible responses for handling an incoming {@link PubsubMessage} handled via + * PubSubMessageHandler. + */ + enum PubSubMessageResponse { + /** + * Response that should be sent when a {@link PubsubMessage} has been successfully processed. + * The service should not send the message again. + */ + ACK("ack"), + + /** + * Response that should be sent when a {@link PubsubMessage} has not been successfully + * processed. The service should resend the message. + */ + NACK("nack"); + + private final String stringValue; + + /** + * Constructor for the {@link PubSubMessageResponse}. + * + * @param stringValue the string representation for the enum value. + */ + PubSubMessageResponse(String stringValue) { + this.stringValue = stringValue; + } + + @Override + public String toString() { + return this.stringValue; + } + } + + /** + * This method accepts and processes an incoming {@link PubsubMessage}. + * + * @param message The incoming {@link PubsubMessage} that should be processed. + * @return a {@link PubSubMessageResponse} indicating if the message was processed successfully. + */ + PubSubMessageResponse handlePubSubMessage(PubsubMessage message); + + /** + * This method is responsible for doing any cleanup tasks required for the {@link + * PubSubMessageHandler} when handler is no longer required. + */ + void cleanupMessageHandler(); +} diff --git a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubPullServer.java b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubPullServer.java new file mode 100644 index 00000000..98320f00 --- /dev/null +++ b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubPullServer.java @@ -0,0 +1,88 @@ +/* + * Copyright 2022 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.opentelemetry.endtoend; + +import com.google.cloud.pubsub.v1.Subscriber; +import com.google.common.util.concurrent.MoreExecutors; + +/** + * A {@link PubSubServer} that can handle running the integration test scenarios when {@link + * Constants#SUBSCRIPTION_MODE} points to 'pull' mode. + * + *

This kind of {@link PubSubServer} works well with Google Cloud's non-serverless compute + * offerings like GCE and GKE. This server should be avoided for running tests in serverless + * environments like CloudRun. + * + *

This class is responsible for the following: + * + *

    + *
  • Setting up a PubSub {@link Subscriber} with the appropriate subscription name and {@link + * com.google.cloud.pubsub.v1.MessageReceiver}. + *
  • Attaching a failure listener to subscriber to get causes for failures. + *
+ */ +public class PubSubPullServer implements PubSubServer { + + private final PubSubMessageHandler pubSubMessageHandler; + private final Subscriber subscriber; + + /** + * Public constructor for {@link PubSubPullServer}. + * + * @param pubSubMessageHandler The {@link PubSubMessageHandler} that will be used to process + * incoming {@link com.google.pubsub.v1.PubsubMessage}s. + */ + public PubSubPullServer(PubSubMessageHandler pubSubMessageHandler) { + this.pubSubMessageHandler = pubSubMessageHandler; + this.subscriber = + Subscriber.newBuilder( + Constants.getRequestSubscription(), + (message, consumer) -> { + PubSubMessageHandler.PubSubMessageResponse response = + pubSubMessageHandler.handlePubSubMessage(message); + if (response == PubSubMessageHandler.PubSubMessageResponse.ACK) { + consumer.ack(); + } else { + consumer.nack(); + } + }) + .build(); + this.subscriber.addListener( + new Subscriber.Listener() { + @Override + public void failed(Subscriber.State from, Throwable failure) { + // Handle failure. This is called when the Subscriber encountered a fatal error and is + // shutting down. + System.err.println(failure.getMessage()); + } + }, + MoreExecutors.directExecutor()); + } + + @Override + public void close() { + if (subscriber != null) { + subscriber.stopAsync(); + subscriber.awaitTerminated(); + } + pubSubMessageHandler.cleanupMessageHandler(); + } + + @Override + public void start() { + subscriber.startAsync().awaitRunning(); + } +} diff --git a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubPushServer.java b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubPushServer.java new file mode 100644 index 00000000..a444ee21 --- /dev/null +++ b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubPushServer.java @@ -0,0 +1,233 @@ +/* + * Copyright 2022 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.opentelemetry.endtoend; + +import com.google.cloud.opentelemetry.endtoend.PubSubMessageHandler.PubSubMessageResponse; +import com.google.common.util.concurrent.MoreExecutors; +import com.google.gson.Gson; +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.google.pubsub.v1.PubsubMessage; +import com.sun.net.httpserver.Headers; +import com.sun.net.httpserver.HttpExchange; +import com.sun.net.httpserver.HttpHandler; +import com.sun.net.httpserver.HttpServer; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.nio.charset.StandardCharsets; +import java.util.Map; + +/** + * A {@link PubSubServer} that can handle running the integration test scenarios when {@link + * Constants#SUBSCRIPTION_MODE} points to 'push' mode. + * + *

Google cloud's serverless offerings like CloudRun recommend using 'push' mode instead of + * 'pull' for subscriptions in pub/sub since CloudRun requires running code (the instance spun up is + * like a daemon when no request is processing). This means we need a dedicated server actively + * listening on a port instead of waiting on messages being pushed to a topic which can then be + * pulled (this is what happens in 'pull' subscription mode). This server sets up an {@link + * HttpServer} on a specified port which actively listens for incoming requests. + * + *

More information on why 'push' mode is preferred can be seen here. + * + *

This class is responsible for the following: + * + *

    + *
  • Setting up a {@link HttpServer} bound to a specified port, listening for incoming requests. + *
  • Validate incoming HTTP requests and parse them into {@link PubsubMessage}s. + *
  • Hand off the parsed {@link PubsubMessage}s to a specified {@link PubSubMessageHandler}. + *
+ */ +public class PubSubPushServer implements PubSubServer { + + private static final String POST_REQUEST = "POST"; + + private final int port; + private final HttpServer httpServer; + private final HttpHandler rootRequestHandler; + private final PubSubMessageHandler pubsubMessageHandler; + + /** + * Public constructor for the {@link PubSubPushServer}. + * + * @param port The port on which the HTTP server should listen for incoming requests. + * @param pubSubMessageHandler The {@link PubSubMessageHandler} responsible for handling the + * incoming HTTP requests. + */ + public PubSubPushServer(int port, PubSubMessageHandler pubSubMessageHandler) { + this.port = port; + this.pubsubMessageHandler = pubSubMessageHandler; + this.rootRequestHandler = createRootRequestHandler(); + this.httpServer = createHttpServer(); + } + + @Override + public void start() { + this.httpServer.start(); + } + + @Override + public void close() { + httpServer.stop(60); + pubsubMessageHandler.cleanupMessageHandler(); + } + + private PubsubMessage parseIncomingMessage(HttpExchange httpExchange) { + InputStreamReader inputStreamReader = + new InputStreamReader(httpExchange.getRequestBody(), StandardCharsets.UTF_8); + JsonElement jsonRoot = JsonParser.parseReader(inputStreamReader); + String msgStr = jsonRoot.getAsJsonObject().get("message").toString(); + Gson gson = new Gson(); + Message message = gson.fromJson(msgStr, Message.class); + return PubsubMessage.newBuilder().putAllAttributes(message.getAttributes()).build(); + } + + private HttpServer createHttpServer() { + HttpServer httpServer = null; + try { + httpServer = HttpServer.create(new InetSocketAddress(this.port), 0); + httpServer.createContext("/", this.rootRequestHandler); + httpServer.createContext("/ready", createStandardStringResponseHandler("Server Ready")); + httpServer.createContext("/alive", createStandardStringResponseHandler("Server Alive")); + httpServer.setExecutor(MoreExecutors.directExecutor()); + } catch (IOException e) { + e.printStackTrace(); + } + return httpServer; + } + + private HttpHandler createRootRequestHandler() { + return httpExchange -> { + if (httpExchange.getRequestMethod().equals(POST_REQUEST)) { + if (!isRequestJSON(httpExchange)) { + String response = "Expecting request in JSON format"; + httpExchange.sendResponseHeaders(400, response.length()); + OutputStream os = httpExchange.getResponseBody(); + os.write(response.getBytes(StandardCharsets.UTF_8)); + os.close(); + httpExchange.close(); + return; + } + PubsubMessage message = parseIncomingMessage(httpExchange); + PubSubMessageResponse ackOrNack = pubsubMessageHandler.handlePubSubMessage(message); + String finalResponse = ""; + if (ackOrNack.equals(PubSubMessageResponse.ACK)) { + finalResponse = "Success"; + httpExchange.sendResponseHeaders(200, finalResponse.length()); + } else { + finalResponse = "Failure"; + httpExchange.sendResponseHeaders(500, finalResponse.length()); + } + OutputStream os = httpExchange.getResponseBody(); + os.write(finalResponse.getBytes(StandardCharsets.UTF_8)); + os.close(); + httpExchange.close(); + } else { + String response = "Only expecting POST requests"; + httpExchange.sendResponseHeaders(400, response.length()); + OutputStream os = httpExchange.getResponseBody(); + os.write(response.getBytes(StandardCharsets.UTF_8)); + os.close(); + httpExchange.close(); + } + }; + } + + private HttpHandler createStandardStringResponseHandler(String response) { + return httpExchange -> { + httpExchange.sendResponseHeaders(200, response.length()); + OutputStream os = httpExchange.getResponseBody(); + os.write(response.getBytes(StandardCharsets.UTF_8)); + os.close(); + httpExchange.close(); + }; + } + + private boolean isRequestJSON(HttpExchange httpExchange) { + Headers headers = httpExchange.getRequestHeaders(); + String contentType = headers.getFirst("Content-type").split(";")[0].trim(); + return contentType.equalsIgnoreCase("application/json"); + } + + /** + * A POJO class containing equivalent Java representation of the incoming HTTP request's (to + * {@link PubSubPushServer}) JSON form. + */ + public static class Message { + private Map attributes; + private String messageId; + private String publishTime; + + /** + * Parameterized constructor for the class. + * + * @param attributes A mapping of String key-value pairs containing custom fields in the + * request. + * @param messageId The unique ID associated with the incoming request. + * @param publishTime The timestamp at which the request was issued. + */ + public Message(Map attributes, String messageId, String publishTime) { + this.attributes = attributes; + this.messageId = messageId; + this.publishTime = publishTime; + } + + public Message() { + // default constructor + } + + public Map getAttributes() { + return attributes; + } + + public void setAttributes(Map attributes) { + this.attributes = attributes; + } + + public String getMessageId() { + return messageId; + } + + public void setMessageId(String messageId) { + this.messageId = messageId; + } + + public String getPublishTime() { + return publishTime; + } + + public void setPublishTime(String publishTime) { + this.publishTime = publishTime; + } + + @Override + public String toString() { + return "Message{" + + "attributes=" + + attributes + + ", messageId='" + + messageId + + '\'' + + ", publishTime='" + + publishTime + + '\'' + + '}'; + } + } +} diff --git a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubServer.java b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubServer.java new file mode 100644 index 00000000..976fbed5 --- /dev/null +++ b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/PubSubServer.java @@ -0,0 +1,28 @@ +/* + * Copyright 2022 Google + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.google.cloud.opentelemetry.endtoend; + +/** + * Interface that represents a server program capable of processing {@link + * com.google.pubsub.v1.PubsubMessage}s. + */ +public interface PubSubServer extends AutoCloseable { + /** + * Method responsible for starting the server. Once the server is 'started', it should begin + * listening/processing requests. + */ + void start(); +} diff --git a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/ScenarioHandlerManager.java b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/ScenarioHandlerManager.java index 3d93d1e7..c883bfeb 100644 --- a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/ScenarioHandlerManager.java +++ b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/ScenarioHandlerManager.java @@ -98,7 +98,7 @@ private Response detectResource(Request request) { Tracer tracer = sdk.getTestTracer(); Span span = tracer - .spanBuilder("detectResource") + .spanBuilder("resourceDetectionTrace") .setAttribute(Constants.TEST_ID, request.testId()) .startSpan(); try { diff --git a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/Server.java b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/Server.java index 34659c41..59855f0d 100644 --- a/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/Server.java +++ b/e2e-test-server/src/main/java/com/google/cloud/opentelemetry/endtoend/Server.java @@ -18,11 +18,12 @@ import com.google.api.core.ApiFuture; import com.google.api.core.ApiFutureCallback; import com.google.api.core.ApiFutures; -import com.google.cloud.pubsub.v1.AckReplyConsumer; import com.google.cloud.pubsub.v1.Publisher; -import com.google.cloud.pubsub.v1.Subscriber; import com.google.common.util.concurrent.MoreExecutors; import com.google.pubsub.v1.PubsubMessage; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; /** * Server implements the "integration test driver" for this language. @@ -30,55 +31,66 @@ *

It is responsible for the following: * *

    - *
  • Setting up a subscriber queue for inbound "RPC Request" messages - *
  • Converting incoming pub sub messages to {@link Request} - *
  • Setting up a publisher queue for outbound "RPC Response" messages - *
  • Converting from outbound {@link Response} to pubsub messages. - *
  • Handling any/all failures escaping the test scenario. + *
  • Implementing logic for handling incoming {@link PubsubMessage}s. + *
  • Starting the correct server to run integration tests depending on {@link + * Constants#SUBSCRIPTION_MODE}. *
* *

This class includes a main method which runs the integration test driver using locally - * available credentials to acccess pubsub channels. + * available credentials to access pubsub channels. */ -public class Server implements AutoCloseable { - private final ScenarioHandlerManager scenarioHandlers = new ScenarioHandlerManager(); +public class Server implements PubSubMessageHandler { + + private final ScenarioHandlerManager scenarioHandlers; private final Publisher publisher; - private final Subscriber subscriber; public Server() throws Exception { + this.scenarioHandlers = new ScenarioHandlerManager(); this.publisher = Publisher.newBuilder(Constants.getResponseTopic()).build(); - this.subscriber = - Subscriber.newBuilder(Constants.getRequestSubscription(), this::handleMessage).build(); - subscriber.addListener( - new Subscriber.Listener() { - @Override - public void failed(Subscriber.State from, Throwable failure) { - // Handle failure. This is called when the Subscriber encountered a fatal error and is - // shutting down. - System.err.println(failure); - } - }, - MoreExecutors.directExecutor()); } - /** Starts the subcriber pulling requests. */ - public void start() { - subscriber.startAsync().awaitRunning(); - } + @Override + public PubSubMessageResponse handlePubSubMessage(PubsubMessage message) { + if (!message.containsAttributes(Constants.TEST_ID)) { + return PubSubMessageResponse.NACK; + } + String testId = message.getAttributesOrDefault(Constants.TEST_ID, ""); + if (!message.containsAttributes(Constants.SCENARIO)) { + respond( + testId, + Response.invalidArgument( + String.format("Expected attribute \"%s\" is missing", Constants.SCENARIO))); + return PubSubMessageResponse.ACK; + } + String scenario = message.getAttributesOrDefault(Constants.SCENARIO, ""); + Request request = Request.make(testId, message.getAttributesMap(), message.getData()); - /** Closes our subscriptions. */ - public void close() { - if (subscriber != null) { - subscriber.stopAsync(); - subscriber.awaitTerminated(); + // Run the given request/response cycle through a handler and respond with results. + Response response = Response.EMPTY; + try { + response = scenarioHandlers.handleScenario(scenario, request); + } catch (Throwable e) { + e.printStackTrace(System.err); + response = Response.internalError(e); + } finally { + respond(testId, response); } + return PubSubMessageResponse.ACK; + } + + /** + * This method is responsible for doing any cleanup tasks required for the {@link + * PubSubMessageHandler} when handler is no longer required. + */ + @Override + public void cleanupMessageHandler() { if (publisher != null) { publisher.shutdown(); } } /** This method converts from {@link Response} to pubsub and sends out the publisher channel. */ - public void respond(final String testId, final Response response) { + private void respond(final String testId, final Response response) { final PubsubMessage message = PubsubMessage.newBuilder() .putAllAttributes(response.headers()) @@ -93,50 +105,33 @@ public void respond(final String testId, final Response response) { public void onSuccess(String messageId) {} public void onFailure(Throwable t) { - System.out.println("failed to publish response to test: " + testId); t.printStackTrace(); } }, MoreExecutors.directExecutor()); - } - - /** Execute a scenario based on the incoming message from the test runner. */ - public void handleMessage(PubsubMessage message, AckReplyConsumer consumer) { - if (!message.containsAttributes(Constants.TEST_ID)) { - consumer.nack(); - return; - } - String testId = message.getAttributesOrDefault(Constants.TEST_ID, ""); - if (!message.containsAttributes(Constants.SCENARIO)) { - respond( - testId, - Response.invalidArgument( - String.format("Expected attribute \"%s\" is missing", Constants.SCENARIO))); - consumer.ack(); - return; + try { + // Wait for the future to get completed. + // This prevents cloud functions from exiting too quickly + messageIdFuture.get(30, TimeUnit.SECONDS); + } catch (InterruptedException | ExecutionException | TimeoutException e) { + e.printStackTrace(); } - String scenario = message.getAttributesOrDefault(Constants.SCENARIO, ""); - Request request = Request.make(testId, message.getAttributesMap(), message.getData()); + } - // Run the given request/response cycle through a handler and respond with results. - Response response = Response.EMPTY; - try { - response = scenarioHandlers.handleScenario(scenario, request); - } catch (Throwable e) { - e.printStackTrace(System.err); - response = Response.internalError(e); - } finally { - respond(testId, response); - consumer.ack(); + private static PubSubServer createPubSubServer(Server server) { + if (Constants.SUBSCRIPTION_MODE.equals(Constants.SUBSCRIPTION_MODE_PULL)) { + return new PubSubPullServer(server); } + return new PubSubPushServer(Integer.parseInt(Constants.PUSH_PORT), server); } /** Runs our server. */ public static void main(String[] args) throws Exception { - try (Server server = new Server()) { - server.start(); - // Docs for Subscriber recommend doing this to block main thread while daemon thread consumes - // stuff. + Server server = new Server(); + try (PubSubServer pubSubServer = createPubSubServer(server)) { + pubSubServer.start(); + // Docs for Subscriber recommend doing this to block main thread while daemon thread + // consumes stuff. for (; ; ) { Thread.sleep(Long.MAX_VALUE); } diff --git a/e2e.Dockerfile b/e2e.Dockerfile index f0e77767..898bfb2a 100644 --- a/e2e.Dockerfile +++ b/e2e.Dockerfile @@ -14,7 +14,7 @@ # limitations under the License. # Build relative to root of repository i.e. `docker build --file e2e.Dockerfile --tag=$tag ..` -FROM gradle:6.9-jdk11-hotspot as builder +FROM gradle:7.5.1-jdk11 as builder COPY --chown=gradle:gradle . /app/src WORKDIR /app/src diff --git a/examples/autoconf/build.gradle b/examples/autoconf/build.gradle index 488b2fd9..94aba213 100644 --- a/examples/autoconf/build.gradle +++ b/examples/autoconf/build.gradle @@ -23,8 +23,8 @@ plugins { description = 'Examples for using java autoconfiguration and Google Cloud Operations' dependencies { - compile(libraries.opentelemetry_api) - compile(libraries.opentelemetry_sdk_metrics) + implementation(libraries.opentelemetry_api) + implementation(libraries.opentelemetry_sdk_metrics) runtimeOnly(libraries.opentelemetry_sdk_autoconf) runtimeOnly(libraries.opentelemetry_sdk) runtimeOnly project(':exporter-auto') diff --git a/examples/metrics/build.gradle b/examples/metrics/build.gradle index 10a71674..f225721b 100644 --- a/examples/metrics/build.gradle +++ b/examples/metrics/build.gradle @@ -31,9 +31,9 @@ mainClassName = 'com.google.cloud.opentelemetry.example.metrics.MetricsExporterE description = 'Examples for Cloud Monitoring Exporter' dependencies { - compile(libraries.opentelemetry_api) - compile(libraries.opentelemetry_sdk_metrics) - compile(libraries.google_cloud_monitoring) - compile project(':exporter-metrics') + implementation(libraries.opentelemetry_api) + implementation(libraries.opentelemetry_sdk_metrics) + implementation(libraries.google_cloud_monitoring) + implementation project(':exporter-metrics') runtimeOnly project(':detector-resources') } diff --git a/examples/resource/build.gradle b/examples/resource/build.gradle index dfc84be4..735802d0 100644 --- a/examples/resource/build.gradle +++ b/examples/resource/build.gradle @@ -23,9 +23,9 @@ plugins { description = 'Examples for showing resource detection in various GCP environments.' dependencies { - compile project(':detector-resources') - compile(libraries.opentelemetry_sdk_autoconf) - compile(libraries.opentelemetry_sdk_resources) + implementation project(':detector-resources') + implementation(libraries.opentelemetry_sdk_autoconf) + implementation(libraries.opentelemetry_sdk_resources) } mainClassName = 'com.google.cloud.opentelemetry.example.resource.ResourceExample' diff --git a/examples/resource/src/main/java/com/google/cloud/opentelemetry/example/resource/ResourceExample.java b/examples/resource/src/main/java/com/google/cloud/opentelemetry/example/resource/ResourceExample.java index 3580d929..d11f765f 100644 --- a/examples/resource/src/main/java/com/google/cloud/opentelemetry/example/resource/ResourceExample.java +++ b/examples/resource/src/main/java/com/google/cloud/opentelemetry/example/resource/ResourceExample.java @@ -15,7 +15,7 @@ */ package com.google.cloud.opentelemetry.example.resource; -import com.google.cloud.opentelemetry.detectors.GKEResource; +import com.google.cloud.opentelemetry.detectors.GCPResource; import io.opentelemetry.sdk.autoconfigure.AutoConfiguredOpenTelemetrySdk; public class ResourceExample { @@ -29,7 +29,7 @@ public static void main(String[] args) { .getResource(); System.out.println(autoResource.getAttributes()); System.out.println("Detecting resource: hardcoded"); - GKEResource resource = new GKEResource(); + GCPResource resource = new GCPResource(); System.out.println(resource.getAttributes()); } } diff --git a/examples/trace/build.gradle b/examples/trace/build.gradle index 8d2e069a..75e5f1ab 100644 --- a/examples/trace/build.gradle +++ b/examples/trace/build.gradle @@ -24,10 +24,10 @@ mainClassName = 'com.google.cloud.opentelemetry.example.trace.TraceExporterExamp description = 'Examples for Cloud Trace Exporter' dependencies { - compile(libraries.opentelemetry_api) - compile(libraries.opentelemetry_sdk) - compile(libraries.google_cloud_trace) - compile project(':exporter-trace') + implementation(libraries.opentelemetry_api) + implementation(libraries.opentelemetry_sdk) + implementation(libraries.google_cloud_trace) + implementation project(':exporter-trace') runtimeOnly project(':detector-resources') } diff --git a/exporters/auto/build.gradle b/exporters/auto/build.gradle index 0a740d26..b32fc695 100644 --- a/exporters/auto/build.gradle +++ b/exporters/auto/build.gradle @@ -44,6 +44,7 @@ dependencies { shadowJar{ classifier = null + mergeServiceFiles() } publishing { diff --git a/exporters/metrics/README.md b/exporters/metrics/README.md index e135b6a8..45f65138 100644 --- a/exporters/metrics/README.md +++ b/exporters/metrics/README.md @@ -61,7 +61,7 @@ MeterProvider provider = SdkMeterProvider.builder() // Rate at which data can be written to a single time series: one point each 10 // seconds. PeriodicMetricReader.builder(metricExporter) - .setImterval(java.time.Duration.ofSeconds(20)) + .setInterval(java.time.Duration.ofSeconds(20)) .build()) .build(); ``` @@ -70,10 +70,10 @@ MeterProvider provider = SdkMeterProvider.builder() | ------------- | -------------------- | ------------ | ----------- | ------- | | projectId | GOOGLE_CLOUD_PROJECT or GOOGLE_APPLICATION_CREDENTIALS | ??? | The cloud project id. This is autodiscovered. | The autodiscovered value. | | credentials | GOOGLE_APPLICATION_CREDENTIALS | N/A | Credentials to use when talking to Cloud Monitoring API. | App Engine, Cloud Shell, GCE built-in or provided by `gcloud auth application-default login` | -| deadline | ??? | ??? | The deadline limit on export calls to Cloud Monitoring API | 10 seconds | +| deadline | ??? | ??? | The deadline limit on export calls to Cloud Monitoring API | 12 seconds | | metricDescriptorStrategy | ??? | ??? | How to adapt OpenTelemetry metric definition into google cloud. `ALWAYS_SEND` will try to create metric descriptors on every export. `SEND_ONCE` will try to create metric descriptors once per Java instance/classloader. `NEVER_SEND` will rely on Cloud Monitoring's auto-generated MetricDescriptors from time series. | `SEND_ONCE` | [maven-image]: https://maven-badges.herokuapp.com/maven-central/com.google.cloud.opentelemetry/exporter-metrics/badge.svg -[maven-url]: https://maven-badges.herokuapp.com/maven-central/com.google.cloud.opentelemetry/exporter-metrics \ No newline at end of file +[maven-url]: https://maven-badges.herokuapp.com/maven-central/com.google.cloud.opentelemetry/exporter-metrics diff --git a/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/AggregateByLabelMetricTimeSeriesBuilder.java b/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/AggregateByLabelMetricTimeSeriesBuilder.java index 0d05ac65..6a858bca 100644 --- a/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/AggregateByLabelMetricTimeSeriesBuilder.java +++ b/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/AggregateByLabelMetricTimeSeriesBuilder.java @@ -45,14 +45,16 @@ public final class AggregateByLabelMetricTimeSeriesBuilder implements MetricTime private final Map descriptors = new HashMap<>(); private final Map pendingTimeSeries = new HashMap<>(); private final String projectId; + private final String prefix; - public AggregateByLabelMetricTimeSeriesBuilder(String projectId) { + public AggregateByLabelMetricTimeSeriesBuilder(String projectId, String prefix) { this.projectId = projectId; + this.prefix = prefix; } @Override public void recordPoint(MetricData metric, LongPointData point) { - MetricDescriptor descriptor = mapMetricDescriptor(metric, point); + MetricDescriptor descriptor = mapMetricDescriptor(this.prefix, metric, point); if (descriptor == null) { // Unsupported type. return; @@ -71,7 +73,7 @@ public void recordPoint(MetricData metric, LongPointData point) { @Override public void recordPoint(MetricData metric, DoublePointData point) { - MetricDescriptor descriptor = mapMetricDescriptor(metric, point); + MetricDescriptor descriptor = mapMetricDescriptor(this.prefix, metric, point); if (descriptor == null) { // Unsupported type. return; @@ -89,7 +91,7 @@ public void recordPoint(MetricData metric, DoublePointData point) { @Override public void recordPoint(MetricData metric, HistogramPointData point) { - MetricDescriptor descriptor = mapMetricDescriptor(metric, point); + MetricDescriptor descriptor = mapMetricDescriptor(this.prefix, metric, point); if (descriptor == null) { // Unsupported type. return; diff --git a/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/GoogleCloudMetricExporter.java b/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/GoogleCloudMetricExporter.java index 115d2eb9..6c93e10b 100644 --- a/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/GoogleCloudMetricExporter.java +++ b/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/GoogleCloudMetricExporter.java @@ -58,11 +58,16 @@ public class GoogleCloudMetricExporter private final CloudMetricClient metricServiceClient; private final String projectId; + private final String prefix; private final MetricDescriptorStrategy metricDescriptorStrategy; GoogleCloudMetricExporter( - String projectId, CloudMetricClient client, MetricDescriptorStrategy descriptorStrategy) { + String projectId, + String prefix, + CloudMetricClient client, + MetricDescriptorStrategy descriptorStrategy) { this.projectId = projectId; + this.prefix = prefix; this.metricServiceClient = client; this.metricDescriptorStrategy = descriptorStrategy; } @@ -75,6 +80,7 @@ public static GoogleCloudMetricExporter createWithDefaultConfiguration() throws public static GoogleCloudMetricExporter createWithConfiguration(MetricConfiguration configuration) throws IOException { String projectId = configuration.getProjectId(); + String prefix = configuration.getPrefix(); MetricServiceSettings.Builder builder = MetricServiceSettings.newBuilder(); // For testing, we need to hack around our gRPC config. if (configuration.getInsecureEndpoint()) { @@ -103,6 +109,7 @@ public static GoogleCloudMetricExporter createWithConfiguration(MetricConfigurat return new GoogleCloudMetricExporter( projectId, + prefix, new CloudMetricClientImpl(MetricServiceClient.create(builder.build())), configuration.getDescriptorStrategy()); } @@ -110,9 +117,11 @@ public static GoogleCloudMetricExporter createWithConfiguration(MetricConfigurat @VisibleForTesting static GoogleCloudMetricExporter createWithClient( String projectId, + String prefix, CloudMetricClient metricServiceClient, MetricDescriptorStrategy descriptorStrategy) { - return new GoogleCloudMetricExporter(projectId, metricServiceClient, descriptorStrategy); + return new GoogleCloudMetricExporter( + projectId, prefix, metricServiceClient, descriptorStrategy); } private void exportDescriptor(MetricDescriptor descriptor) { @@ -135,7 +144,8 @@ public CompletableResultCode export(Collection metrics) { // 1. Iterate over all points in the set of metrics to export // 2. Attempt to register MetricDescriptors (using configured strategy) // 3. Fire the set of time series off. - MetricTimeSeriesBuilder builder = new AggregateByLabelMetricTimeSeriesBuilder(projectId); + MetricTimeSeriesBuilder builder = + new AggregateByLabelMetricTimeSeriesBuilder(projectId, prefix); for (final MetricData metricData : metrics) { // Extract all the underlying points. switch (metricData.getType()) { diff --git a/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/MetricConfiguration.java b/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/MetricConfiguration.java index 22b2a6cb..8f5e74a5 100644 --- a/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/MetricConfiguration.java +++ b/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/MetricConfiguration.java @@ -36,10 +36,12 @@ @AutoValue @Immutable public abstract class MetricConfiguration { + static final String DEFAULT_PREFIX = "workload.googleapis.com"; private static final String DEFAULT_PROJECT_ID = Strings.nullToEmpty(ServiceOptions.getDefaultProjectId()); - private static final Duration DEFAULT_DEADLINE = Duration.ofSeconds(10, 0); + private static final Duration DEFAULT_DEADLINE = + Duration.ofSeconds(12, 0); // Consistent with Cloud Monitoring's timeout MetricConfiguration() {} @@ -60,10 +62,20 @@ public abstract class MetricConfiguration { */ public abstract String getProjectId(); + /** + * Returns the prefix prepended to metric names. + * + * @see Custom Metrics + * Identifiers + *

Defaults to workload.googleapis.com. + * @return the prefix to attach to metrics. + */ + public abstract String getPrefix(); + /** * Returns the deadline for exporting to Cloud Monitoring backend. * - *

Default value is 10 seconds. + *

Default value is {{@link MetricConfiguration#DEFAULT_DEADLINE}. * * @return the export deadline. */ @@ -105,6 +117,7 @@ public abstract class MetricConfiguration { public static Builder builder() { return new AutoValue_MetricConfiguration.Builder() .setProjectId(DEFAULT_PROJECT_ID) + .setPrefix(DEFAULT_PREFIX) .setDeadline(DEFAULT_DEADLINE) .setDescriptorStrategy(MetricDescriptorStrategy.SEND_ONCE) .setInsecureEndpoint(false) @@ -124,6 +137,9 @@ public abstract static class Builder { /** Set the GCP project where metrics should be writtten. */ public abstract Builder setProjectId(String projectId); + /** Set the prefix prepended to metric names. */ + public abstract Builder setPrefix(String prefix); + /** Set the credentials to use when writing metrics. */ public abstract Builder setCredentials(Credentials newCredentials); diff --git a/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/MetricTranslator.java b/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/MetricTranslator.java index 00af9eb8..08a51c5b 100644 --- a/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/MetricTranslator.java +++ b/exporters/metrics/src/main/java/com/google/cloud/opentelemetry/metric/MetricTranslator.java @@ -37,6 +37,7 @@ import io.opentelemetry.sdk.metrics.data.MetricData; import io.opentelemetry.sdk.metrics.data.MetricDataType; import io.opentelemetry.sdk.metrics.data.SumData; +import java.nio.file.Paths; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -48,7 +49,6 @@ public final class MetricTranslator { private static final Logger logger = LoggerFactory.getLogger(MetricTranslator.class); - static final String DESCRIPTOR_TYPE_URL = "workload.googleapis.com/"; static final Set KNOWN_DOMAINS = ImmutableSet.of("googleapis.com", "kubernetes.io", "istio.io", "knative.dev"); static final long NANO_PER_SECOND = (long) 1e9; @@ -69,12 +69,12 @@ static String cleanAttributeKey(String key) { } static MetricDescriptor mapMetricDescriptor( - MetricData metric, io.opentelemetry.sdk.metrics.data.PointData metricPoint) { + String prefix, MetricData metric, io.opentelemetry.sdk.metrics.data.PointData metricPoint) { MetricDescriptor.Builder builder = MetricDescriptor.newBuilder() .setDisplayName(metric.getName()) .setDescription(metric.getDescription()) - .setType(mapMetricType(metric.getName())) + .setType(mapMetricType(metric.getName(), prefix)) .setUnit(metric.getUnit()); metricPoint .getAttributes() @@ -136,13 +136,13 @@ private static MetricDescriptor fillSumType(SumData sum, MetricDescriptor.Bui } } - private static String mapMetricType(String instrumentName) { + private static String mapMetricType(String instrumentName, String prefix) { for (String domain : KNOWN_DOMAINS) { if (instrumentName.contains(domain)) { return instrumentName; } } - return DESCRIPTOR_TYPE_URL + instrumentName; + return Paths.get(prefix, instrumentName).toString(); } static LabelDescriptor mapAttribute(AttributeKey key, Object value) { diff --git a/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/EndToEndTest.java b/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/EndToEndTest.java index 3b2d729d..8d690c51 100644 --- a/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/EndToEndTest.java +++ b/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/EndToEndTest.java @@ -45,7 +45,7 @@ private static class CloudOperationsMockContainer builder .from("golang:1.17") .run( - "go install github.com/googleinterns/cloud-operations-api-mock/cmd@latest") + "go install github.com/googleinterns/cloud-operations-api-mock/cmd@v2-alpha") .cmd("cmd --address=:8080") .build())); this.withExposedPorts(8080).waitingFor(Wait.forLogMessage(".*Listening on.*\\n", 1)); diff --git a/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/GoogleCloudMetricExporterTest.java b/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/GoogleCloudMetricExporterTest.java index 37233de8..b841bfdb 100644 --- a/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/GoogleCloudMetricExporterTest.java +++ b/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/GoogleCloudMetricExporterTest.java @@ -28,7 +28,7 @@ import static com.google.cloud.opentelemetry.metric.FakeData.aSpanId; import static com.google.cloud.opentelemetry.metric.FakeData.aTraceId; import static com.google.cloud.opentelemetry.metric.FakeData.anInstrumentationLibraryInfo; -import static com.google.cloud.opentelemetry.metric.MetricTranslator.DESCRIPTOR_TYPE_URL; +import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_PREFIX; import static com.google.cloud.opentelemetry.metric.MetricTranslator.METRIC_DESCRIPTOR_TIME_UNIT; import static com.google.cloud.opentelemetry.metric.MetricTranslator.NANO_PER_SECOND; import static org.junit.Assert.assertEquals; @@ -111,7 +111,7 @@ public void testCreateWithConfigurationSucceeds() throws IOException { public void testExportSendsAllDescriptorsOnce() { GoogleCloudMetricExporter exporter = GoogleCloudMetricExporter.createWithClient( - aProjectId, mockClient, MetricDescriptorStrategy.SEND_ONCE); + aProjectId, DEFAULT_PREFIX, mockClient, MetricDescriptorStrategy.SEND_ONCE); CompletableResultCode result = exporter.export(ImmutableList.of(aMetricData, aHistogram)); assertTrue(result.isSuccess()); CompletableResultCode result2 = exporter.export(ImmutableList.of(aMetricData, aHistogram)); @@ -127,8 +127,8 @@ public void testExportSendsAllDescriptorsOnce() { metricDescriptorCaptor.getAllValues().stream() .map(d -> d.getMetricDescriptor().getType()) .collect(Collectors.toSet()); - assertTrue(metricDescriptorTypes.contains(DESCRIPTOR_TYPE_URL + aMetricData.getName())); - assertTrue(metricDescriptorTypes.contains(DESCRIPTOR_TYPE_URL + aHistogram.getName())); + assertTrue(metricDescriptorTypes.contains(DEFAULT_PREFIX + "/" + aMetricData.getName())); + assertTrue(metricDescriptorTypes.contains(DEFAULT_PREFIX + "/" + aHistogram.getName())); } @Test @@ -136,7 +136,7 @@ public void testExportSucceeds() { MetricDescriptor expectedDescriptor = MetricDescriptor.newBuilder() .setDisplayName(aMetricData.getName()) - .setType(DESCRIPTOR_TYPE_URL + aMetricData.getName()) + .setType(DEFAULT_PREFIX + "/" + aMetricData.getName()) .addLabels( LabelDescriptor.newBuilder() .setKey("label1") @@ -193,7 +193,7 @@ public void testExportSucceeds() { GoogleCloudMetricExporter exporter = GoogleCloudMetricExporter.createWithClient( - aProjectId, mockClient, MetricDescriptorStrategy.ALWAYS_SEND); + aProjectId, DEFAULT_PREFIX, mockClient, MetricDescriptorStrategy.ALWAYS_SEND); CompletableResultCode result = exporter.export(ImmutableList.of(aMetricData)); verify(mockClient, times(1)).createMetricDescriptor(metricDescriptorCaptor.capture()); @@ -212,7 +212,7 @@ public void testExportWithHistogram_Succeeds() { MetricDescriptor expectedDescriptor = MetricDescriptor.newBuilder() .setDisplayName(aHistogram.getName()) - .setType(DESCRIPTOR_TYPE_URL + aHistogram.getName()) + .setType(DEFAULT_PREFIX + "/" + aHistogram.getName()) .addLabels( LabelDescriptor.newBuilder().setKey("test").setValueType(ValueType.STRING).build()) .setMetricKind(MetricKind.CUMULATIVE) @@ -290,7 +290,7 @@ public void testExportWithHistogram_Succeeds() { .build(); GoogleCloudMetricExporter exporter = GoogleCloudMetricExporter.createWithClient( - aProjectId, mockClient, MetricDescriptorStrategy.ALWAYS_SEND); + aProjectId, DEFAULT_PREFIX, mockClient, MetricDescriptorStrategy.ALWAYS_SEND); CompletableResultCode result = exporter.export(ImmutableList.of(aHistogram)); verify(mockClient, times(1)).createMetricDescriptor(metricDescriptorCaptor.capture()); verify(mockClient, times(1)) @@ -306,7 +306,7 @@ public void testExportWithHistogram_Succeeds() { public void testExportWithNonSupportedMetricTypeReturnsFailure() { GoogleCloudMetricExporter exporter = GoogleCloudMetricExporter.createWithClient( - aProjectId, mockClient, MetricDescriptorStrategy.ALWAYS_SEND); + aProjectId, DEFAULT_PREFIX, mockClient, MetricDescriptorStrategy.ALWAYS_SEND); MetricData metricData = ImmutableMetricData.createDoubleSummary( diff --git a/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/MetricTranslatorTest.java b/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/MetricTranslatorTest.java index 0c63ecf7..16d1854b 100644 --- a/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/MetricTranslatorTest.java +++ b/exporters/metrics/src/test/java/com/google/cloud/opentelemetry/metric/MetricTranslatorTest.java @@ -22,7 +22,7 @@ import static com.google.cloud.opentelemetry.metric.FakeData.aLongPoint; import static com.google.cloud.opentelemetry.metric.FakeData.aMetricData; import static com.google.cloud.opentelemetry.metric.FakeData.anInstrumentationLibraryInfo; -import static com.google.cloud.opentelemetry.metric.MetricTranslator.DESCRIPTOR_TYPE_URL; +import static com.google.cloud.opentelemetry.metric.MetricConfiguration.DEFAULT_PREFIX; import static com.google.cloud.opentelemetry.metric.MetricTranslator.METRIC_DESCRIPTOR_TIME_UNIT; import static io.opentelemetry.api.common.AttributeKey.booleanKey; import static io.opentelemetry.api.common.AttributeKey.longKey; @@ -54,10 +54,11 @@ @RunWith(JUnit4.class) public class MetricTranslatorTest { + static final String customPrefix = "custom.googleapis.com"; @Test public void testMapMetricSucceeds() { - String type = "workload.googleapis.com/" + anInstrumentationLibraryInfo.getName(); + String type = DEFAULT_PREFIX + "/" + anInstrumentationLibraryInfo.getName(); Builder expectedMetricBuilder = Metric.newBuilder().setType(type); aLongPoint @@ -70,7 +71,7 @@ public void testMapMetricSucceeds() { @Test public void testMapMetricWithWierdAttributeNameSucceeds() { - String type = "workload.googleapis.com/" + anInstrumentationLibraryInfo.getName(); + String type = DEFAULT_PREFIX + "/" + anInstrumentationLibraryInfo.getName(); Attributes attributes = io.opentelemetry.api.common.Attributes.of(stringKey("test.bad"), "value"); Metric expectedMetric = @@ -84,7 +85,7 @@ public void testMapMetricDescriptorSucceeds() { MetricDescriptor.Builder expectedDescriptor = MetricDescriptor.newBuilder() .setDisplayName(aMetricData.getName()) - .setType(DESCRIPTOR_TYPE_URL + aMetricData.getName()) + .setType(DEFAULT_PREFIX + "/" + aMetricData.getName()) .addLabels(LabelDescriptor.newBuilder().setKey("label1").setValueType(ValueType.STRING)) .addLabels(LabelDescriptor.newBuilder().setKey("label2").setValueType(ValueType.BOOL)) .setUnit(METRIC_DESCRIPTOR_TIME_UNIT) @@ -93,7 +94,25 @@ public void testMapMetricDescriptorSucceeds() { .setValueType(MetricDescriptor.ValueType.INT64); MetricDescriptor actualDescriptor = - MetricTranslator.mapMetricDescriptor(aMetricData, aLongPoint); + MetricTranslator.mapMetricDescriptor(DEFAULT_PREFIX, aMetricData, aLongPoint); + assertEquals(expectedDescriptor.build(), actualDescriptor); + } + + @Test + public void testMapMetricDescriptorCustomPrefixSucceeds() { + MetricDescriptor.Builder expectedDescriptor = + MetricDescriptor.newBuilder() + .setDisplayName(aMetricData.getName()) + .setType(customPrefix + "/" + aMetricData.getName()) + .addLabels(LabelDescriptor.newBuilder().setKey("label1").setValueType(ValueType.STRING)) + .addLabels(LabelDescriptor.newBuilder().setKey("label2").setValueType(ValueType.BOOL)) + .setUnit(METRIC_DESCRIPTOR_TIME_UNIT) + .setDescription(aMetricData.getDescription()) + .setMetricKind(MetricKind.CUMULATIVE) + .setValueType(MetricDescriptor.ValueType.INT64); + + MetricDescriptor actualDescriptor = + MetricTranslator.mapMetricDescriptor(customPrefix, aMetricData, aLongPoint); assertEquals(expectedDescriptor.build(), actualDescriptor); } @@ -114,7 +133,7 @@ public void testMapMetricDescriptorNonMonotonicSumIsGauage() { MetricDescriptor.Builder expectedDescriptor = MetricDescriptor.newBuilder() .setDisplayName(metricData.getName()) - .setType(DESCRIPTOR_TYPE_URL + metricData.getName()) + .setType(DEFAULT_PREFIX + "/" + metricData.getName()) .addLabels(LabelDescriptor.newBuilder().setKey("label1").setValueType(ValueType.STRING)) .addLabels(LabelDescriptor.newBuilder().setKey("label2").setValueType(ValueType.BOOL)) .setUnit(METRIC_DESCRIPTOR_TIME_UNIT) @@ -123,7 +142,7 @@ public void testMapMetricDescriptorNonMonotonicSumIsGauage() { .setValueType(MetricDescriptor.ValueType.INT64); MetricDescriptor actualDescriptor = - MetricTranslator.mapMetricDescriptor(metricData, aLongPoint); + MetricTranslator.mapMetricDescriptor(DEFAULT_PREFIX, metricData, aLongPoint); assertEquals(expectedDescriptor.build(), actualDescriptor); } @@ -144,7 +163,7 @@ public void testMapMetricDescriptorHistogramIsDistribution() { MetricDescriptor.Builder expectedDescriptor = MetricDescriptor.newBuilder() .setDisplayName(metricData.getName()) - .setType(DESCRIPTOR_TYPE_URL + metricData.getName()) + .setType(DEFAULT_PREFIX + "/" + metricData.getName()) .addLabels(LabelDescriptor.newBuilder().setKey("test").setValueType(ValueType.STRING)) .setUnit(METRIC_DESCRIPTOR_TIME_UNIT) .setDescription(metricData.getDescription()) @@ -152,7 +171,7 @@ public void testMapMetricDescriptorHistogramIsDistribution() { .setValueType(MetricDescriptor.ValueType.DISTRIBUTION); MetricDescriptor actualDescriptor = - MetricTranslator.mapMetricDescriptor(metricData, aHistogramPoint); + MetricTranslator.mapMetricDescriptor(DEFAULT_PREFIX, metricData, aHistogramPoint); assertEquals(expectedDescriptor.build(), actualDescriptor); } @@ -171,7 +190,7 @@ public void testMapMetricDescriptorWithInvalidMetricKindReturnsNull() { ImmutableSummaryData.create(ImmutableList.of(aDoubleSummaryPoint))); MetricDescriptor actualDescriptor = - MetricTranslator.mapMetricDescriptor(metricData, aLongPoint); + MetricTranslator.mapMetricDescriptor(DEFAULT_PREFIX, metricData, aLongPoint); assertNull(actualDescriptor); } @@ -191,7 +210,7 @@ public void testMapMetricDescriptorWithDeltaSumReturnsNull() { true, AggregationTemporality.DELTA, ImmutableList.of(aDoublePoint))); MetricDescriptor actualDescriptor = - MetricTranslator.mapMetricDescriptor(metricData, aLongPoint); + MetricTranslator.mapMetricDescriptor(DEFAULT_PREFIX, metricData, aLongPoint); assertNull(actualDescriptor); } diff --git a/exporters/trace/src/main/java/com/google/cloud/opentelemetry/trace/TraceConfiguration.java b/exporters/trace/src/main/java/com/google/cloud/opentelemetry/trace/TraceConfiguration.java index 5e3c4f1c..df235db3 100644 --- a/exporters/trace/src/main/java/com/google/cloud/opentelemetry/trace/TraceConfiguration.java +++ b/exporters/trace/src/main/java/com/google/cloud/opentelemetry/trace/TraceConfiguration.java @@ -183,7 +183,7 @@ public abstract static class Builder { /** * Sets the map of attribute keys that will be renamed. * - * @param attributeMapping the map of attribute OTEL key -> GCP attribute name. + * @param attributeMapping the map of attribute OTEL key to GCP attribute name. * @return this. */ public abstract Builder setAttributeMapping(ImmutableMap attributeMapping); diff --git a/exporters/trace/src/main/java/com/google/cloud/opentelemetry/trace/TraceExporter.java b/exporters/trace/src/main/java/com/google/cloud/opentelemetry/trace/TraceExporter.java index ed656023..caaaadd7 100644 --- a/exporters/trace/src/main/java/com/google/cloud/opentelemetry/trace/TraceExporter.java +++ b/exporters/trace/src/main/java/com/google/cloud/opentelemetry/trace/TraceExporter.java @@ -21,6 +21,7 @@ import com.google.api.gax.core.NoCredentialsProvider; import com.google.api.gax.grpc.GrpcTransportChannel; import com.google.api.gax.rpc.FixedTransportChannelProvider; +import com.google.api.gax.rpc.HeaderProvider; import com.google.auth.Credentials; import com.google.auth.oauth2.GoogleCredentials; import com.google.cloud.trace.v2.TraceServiceClient; @@ -47,6 +48,10 @@ public class TraceExporter implements SpanExporter { private final String projectId; private final TraceTranslator translator; + private static final Map HEADERS = + Map.of("User-Agent", "opentelemetry-operations-java/" + TraceVersions.EXPORTER_VERSION); + private static final HeaderProvider HEADER_PROVIDER = () -> HEADERS; + public static TraceExporter createWithDefaultConfiguration() throws IOException { TraceConfiguration configuration = TraceConfiguration.builder().build(); return TraceExporter.createWithConfiguration(configuration); @@ -83,6 +88,7 @@ public static TraceExporter createWithConfiguration(TraceConfiguration configura builder.setCredentialsProvider( FixedCredentialsProvider.create(checkNotNull(credentials, "credentials"))); builder.setEndpoint(configuration.getTraceServiceEndpoint()); + builder.setHeaderProvider(HEADER_PROVIDER); } return new TraceExporter( diff --git a/exporters/trace/src/test/java/com/google/cloud/opentelemetry/trace/EndToEndTest.java b/exporters/trace/src/test/java/com/google/cloud/opentelemetry/trace/EndToEndTest.java index 0a5d3942..af7099c5 100644 --- a/exporters/trace/src/test/java/com/google/cloud/opentelemetry/trace/EndToEndTest.java +++ b/exporters/trace/src/test/java/com/google/cloud/opentelemetry/trace/EndToEndTest.java @@ -68,7 +68,7 @@ private static class CloudOperationsMockContainer builder .from("golang:1.17") .run( - "go install github.com/googleinterns/cloud-operations-api-mock/cmd@latest") + "go install github.com/googleinterns/cloud-operations-api-mock/cmd@v2-alpha") .cmd("cmd --address=:8080") .build())); this.withExposedPorts(8080).waitingFor(Wait.forLogMessage(".*Listening on.*\\n", 1)); diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 490fda85..249e5832 100644 Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index dcfb223f..f371643e 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,5 @@ -#Tue Apr 07 13:23:46 AEST 2020 -distributionUrl=https\://services.gradle.org/distributions/gradle-6.3-all.zip distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-7.0-bin.zip zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 2fe81a7d..a69d9cb6 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,78 +17,113 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` +APP_BASE_NAME=${0##*/} # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -97,7 +132,7 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" + JAVACMD=java which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the @@ -105,79 +140,101 @@ location of your Java installation." fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 9109989e..53a6b238 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,7 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -54,7 +54,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -64,38 +64,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/propagators/gcp/src/main/java/com/google/cloud/opentelemetry/propagators/XCloudTraceConfigurablePropagatorProvider.java b/propagators/gcp/src/main/java/com/google/cloud/opentelemetry/propagators/XCloudTraceConfigurablePropagatorProvider.java index 6e305af8..c859febc 100644 --- a/propagators/gcp/src/main/java/com/google/cloud/opentelemetry/propagators/XCloudTraceConfigurablePropagatorProvider.java +++ b/propagators/gcp/src/main/java/com/google/cloud/opentelemetry/propagators/XCloudTraceConfigurablePropagatorProvider.java @@ -28,7 +28,7 @@ * flag behaves subtly different from expectations in both w3c traceparent *and* opentelemetry * propagation. * - * @see {@link OneWayXCloudTraceConfigurablePropagatorProvider} + * @see OneWayXCloudTraceConfigurablePropagatorProvider */ @AutoService(ConfigurablePropagatorProvider.class) public class XCloudTraceConfigurablePropagatorProvider implements ConfigurablePropagatorProvider { diff --git a/propagators/gcp/src/main/java/com/google/cloud/opentelemetry/propagators/XCloudTraceContextPropagator.java b/propagators/gcp/src/main/java/com/google/cloud/opentelemetry/propagators/XCloudTraceContextPropagator.java index 1dde8fe2..bf6a213a 100644 --- a/propagators/gcp/src/main/java/com/google/cloud/opentelemetry/propagators/XCloudTraceContextPropagator.java +++ b/propagators/gcp/src/main/java/com/google/cloud/opentelemetry/propagators/XCloudTraceContextPropagator.java @@ -50,7 +50,7 @@ public final class XCloudTraceContextPropagator implements TextMapPropagator { /** * Constructs a new text map propogator that leverages the X-Cloud-Trace-Context header. * - * @param oneway + * @param oneway boolean to configure if the trace should propagate in a single direction. */ public XCloudTraceContextPropagator(boolean oneway) { this.oneway = oneway; diff --git a/settings.gradle b/settings.gradle index 03bb6df7..6a8f8e31 100644 --- a/settings.gradle +++ b/settings.gradle @@ -17,7 +17,7 @@ pluginManagement { plugins { id "com.diffplug.spotless" version "5.9.0" id 'nebula.release' version '15.2.0' - id "com.github.johnrengelman.shadow" version "6.0.0" + id "com.github.johnrengelman.shadow" version "7.1.2" id 'com.google.cloud.tools.jib' version '3.1.4' } } @@ -75,4 +75,4 @@ project(':propagators-gcp').projectDir = "$rootDir/propagators/gcp" as File project(':shared-resourcemapping').projectDir = - "$rootDir/shared/resourcemapping" as File \ No newline at end of file + "$rootDir/shared/resourcemapping" as File diff --git a/shared/resourcemapping/build.gradle b/shared/resourcemapping/build.gradle index fc8543fe..fd8a8e7c 100644 --- a/shared/resourcemapping/build.gradle +++ b/shared/resourcemapping/build.gradle @@ -40,6 +40,7 @@ shadowJar{ exclude(dependency(libraries.opentelemetry_sdk_common)) exclude(dependency(libraries.opentelemetry_context)) } + mergeServiceFiles() } publishing { diff --git a/shared/resourcemapping/src/main/java/com/google/cloud/opentelemetry/resource/ResourceTranslator.java b/shared/resourcemapping/src/main/java/com/google/cloud/opentelemetry/resource/ResourceTranslator.java index 7193e06b..e4213dc6 100644 --- a/shared/resourcemapping/src/main/java/com/google/cloud/opentelemetry/resource/ResourceTranslator.java +++ b/shared/resourcemapping/src/main/java/com/google/cloud/opentelemetry/resource/ResourceTranslator.java @@ -75,7 +75,10 @@ public static AttributeMapping create( AttributeMapping.create("instance_id", ResourceAttributes.HOST_ID)); private static List K8S_CONTAINER_LABELS = java.util.Arrays.asList( - AttributeMapping.create("location", ResourceAttributes.CLOUD_AVAILABILITY_ZONE), + AttributeMapping.create( + "location", + java.util.Arrays.asList( + ResourceAttributes.CLOUD_AVAILABILITY_ZONE, ResourceAttributes.CLOUD_REGION)), AttributeMapping.create("cluster_name", ResourceAttributes.K8S_CLUSTER_NAME), AttributeMapping.create("namespace_name", ResourceAttributes.K8S_NAMESPACE_NAME), AttributeMapping.create("container_name", ResourceAttributes.K8S_CONTAINER_NAME), @@ -85,6 +88,22 @@ public static AttributeMapping create( AttributeMapping.create("instance_id", ResourceAttributes.HOST_ID), AttributeMapping.create("region", ResourceAttributes.CLOUD_AVAILABILITY_ZONE), AttributeMapping.create("aws_account", ResourceAttributes.CLOUD_ACCOUNT_ID)); + private static List GOOGLE_CLOUD_RUN_INSTANCE_LABELS = + java.util.Arrays.asList( + AttributeMapping.create("location", ResourceAttributes.CLOUD_REGION), + AttributeMapping.create("service_name", ResourceAttributes.FAAS_NAME), + AttributeMapping.create("configuration_name", ResourceAttributes.FAAS_NAME), + AttributeMapping.create("revision_name", ResourceAttributes.FAAS_VERSION)); + private static List GOOGLE_CLOUD_FUNCTION_INSTANCE_LABELS = + java.util.Arrays.asList( + AttributeMapping.create("region", ResourceAttributes.CLOUD_REGION), + AttributeMapping.create("function_name", ResourceAttributes.FAAS_NAME)); + private static List GOOGLE_CLOUD_APP_ENGINE_INSTANCE_LABELS = + java.util.Arrays.asList( + AttributeMapping.create("module_id", ResourceAttributes.FAAS_NAME), + AttributeMapping.create("version_id", ResourceAttributes.FAAS_VERSION), + AttributeMapping.create("instance_id", ResourceAttributes.FAAS_ID), + AttributeMapping.create("location", ResourceAttributes.CLOUD_REGION)); private static List GENERIC_TASK_LABELS = java.util.Arrays.asList( AttributeMapping.create( @@ -109,6 +128,12 @@ public static GcpResource mapResource(Resource resource) { return mapBase(resource, "k8s_container", K8S_CONTAINER_LABELS); case ResourceAttributes.CloudPlatformValues.AWS_EC2: return mapBase(resource, "aws_ec2_instance", AWS_EC2_INSTANCE_LABELS); + case ResourceAttributes.CloudPlatformValues.GCP_CLOUD_RUN: + return mapBase(resource, "cloud_run_revision", GOOGLE_CLOUD_RUN_INSTANCE_LABELS); + case ResourceAttributes.CloudPlatformValues.GCP_CLOUD_FUNCTIONS: + return mapBase(resource, "cloud_function", GOOGLE_CLOUD_FUNCTION_INSTANCE_LABELS); + case ResourceAttributes.CloudPlatformValues.GCP_APP_ENGINE: + return mapBase(resource, "gae_instance", GOOGLE_CLOUD_APP_ENGINE_INSTANCE_LABELS); default: return mapBase(resource, "generic_task", GENERIC_TASK_LABELS); }