mirror of https://github.com/grpc/grpc-go.git
				
				
				
			stats/opentelemetry: Add CSM Plugin Option (#7205)
This commit is contained in:
		
							parent
							
								
									2f52f9e005
								
							
						
					
					
						commit
						0a0abfadb7
					
				| 
						 | 
				
			
			@ -105,8 +105,15 @@ jobs:
 | 
			
		|||
          cd "${GITHUB_WORKSPACE}"
 | 
			
		||||
          for MOD_FILE in $(find . -name 'go.mod' | grep -Ev '^\./go\.mod'); do
 | 
			
		||||
            pushd "$(dirname ${MOD_FILE})"
 | 
			
		||||
            go test ${{ matrix.testflags }} -cpu 1,4 -timeout 2m ./...
 | 
			
		||||
            # Skip OpenTelemetry module if 3 releases ago, Go OpenTelemetry only supports
 | 
			
		||||
            # previous two releases.
 | 
			
		||||
            if [[ "${{ matrix.goversion }}" == "1.20" && "${PWD}" =~ /stats/opentelemetry$ ]]; then
 | 
			
		||||
              echo "Skipping ${MOD_FILE}"
 | 
			
		||||
            else
 | 
			
		||||
               go test ${{ matrix.testflags }} -cpu 1,4 -timeout 2m ./...
 | 
			
		||||
            fi
 | 
			
		||||
            popd
 | 
			
		||||
 | 
			
		||||
          done
 | 
			
		||||
 | 
			
		||||
      # Non-core gRPC tests (examples, interop, etc)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,309 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright 2024 gRPC authors.
 | 
			
		||||
 *
 | 
			
		||||
 * 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 csm contains utilities for Google Cloud Service Mesh observability.
 | 
			
		||||
package csm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"google.golang.org/grpc/grpclog"
 | 
			
		||||
	"google.golang.org/grpc/internal/xds/bootstrap"
 | 
			
		||||
	"google.golang.org/grpc/metadata"
 | 
			
		||||
	"google.golang.org/grpc/stats/opentelemetry/internal"
 | 
			
		||||
 | 
			
		||||
	"google.golang.org/protobuf/proto"
 | 
			
		||||
	"google.golang.org/protobuf/types/known/structpb"
 | 
			
		||||
 | 
			
		||||
	"go.opentelemetry.io/contrib/detectors/gcp"
 | 
			
		||||
	"go.opentelemetry.io/otel/attribute"
 | 
			
		||||
	"go.opentelemetry.io/otel/sdk/resource"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var logger = grpclog.Component("csm-observability-plugin")
 | 
			
		||||
 | 
			
		||||
// pluginOption emits CSM Labels from the environment and metadata exchange
 | 
			
		||||
// for csm channels and all servers.
 | 
			
		||||
//
 | 
			
		||||
// Do not use this directly; use newPluginOption instead.
 | 
			
		||||
type pluginOption struct {
 | 
			
		||||
	// localLabels are the labels that identify the local environment a binary
 | 
			
		||||
	// is run in, and will be emitted from the CSM Plugin Option.
 | 
			
		||||
	localLabels map[string]string
 | 
			
		||||
	// metadataExchangeLabelsEncoded are the metadata exchange labels to be sent
 | 
			
		||||
	// as the value of metadata key "x-envoy-peer-metadata" in proto wire format
 | 
			
		||||
	// and base 64 encoded. This gets sent out from all the servers running in
 | 
			
		||||
	// this process and for csm channels.
 | 
			
		||||
	metadataExchangeLabelsEncoded string
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// newPluginOption returns a new pluginOption with local labels and metadata
 | 
			
		||||
// exchange labels derived from the environment.
 | 
			
		||||
func newPluginOption(ctx context.Context) internal.PluginOption {
 | 
			
		||||
	localLabels, metadataExchangeLabelsEncoded := constructMetadataFromEnv(ctx)
 | 
			
		||||
 | 
			
		||||
	return &pluginOption{
 | 
			
		||||
		localLabels:                   localLabels,
 | 
			
		||||
		metadataExchangeLabelsEncoded: metadataExchangeLabelsEncoded,
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// NewLabelsMD returns a metadata.MD with the CSM labels as an encoded protobuf
 | 
			
		||||
// Struct as the value of "x-envoy-peer-metadata".
 | 
			
		||||
func (cpo *pluginOption) GetMetadata() metadata.MD {
 | 
			
		||||
	return metadata.New(map[string]string{
 | 
			
		||||
		metadataExchangeKey: cpo.metadataExchangeLabelsEncoded,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// GetLabels gets the CSM peer labels from the metadata provided. It returns
 | 
			
		||||
// "unknown" for labels not found. Labels returned depend on the remote type.
 | 
			
		||||
// Additionally, local labels determined at initialization time are appended to
 | 
			
		||||
// labels returned, in addition to the optionalLabels provided.
 | 
			
		||||
func (cpo *pluginOption) GetLabels(md metadata.MD) map[string]string {
 | 
			
		||||
	labels := map[string]string{ // Remote labels if type is unknown (i.e. unset or error processing x-envoy-peer-metadata)
 | 
			
		||||
		"csm.remote_workload_type":              "unknown",
 | 
			
		||||
		"csm.remote_workload_canonical_service": "unknown",
 | 
			
		||||
	}
 | 
			
		||||
	// Append the local labels.
 | 
			
		||||
	for k, v := range cpo.localLabels {
 | 
			
		||||
		labels[k] = v
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	val := md.Get("x-envoy-peer-metadata")
 | 
			
		||||
	// This can't happen if corresponding csm client because of proto wire
 | 
			
		||||
	// format encoding, but since it is arbitrary off the wire be safe.
 | 
			
		||||
	if len(val) != 1 {
 | 
			
		||||
		logger.Warningf("length of md values of \"x-envoy-peer-metadata\" is not 1, is %v", len(val))
 | 
			
		||||
		return labels
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	protoWireFormat, err := base64.RawStdEncoding.DecodeString(val[0])
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		logger.Warningf("error base 64 decoding value of \"x-envoy-peer-metadata\": %v", err)
 | 
			
		||||
		return labels
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	spb := &structpb.Struct{}
 | 
			
		||||
	if err := proto.Unmarshal(protoWireFormat, spb); err != nil {
 | 
			
		||||
		logger.Warningf("error unmarshalling value of \"x-envoy-peer-metadata\" into proto: %v", err)
 | 
			
		||||
		return labels
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	fields := spb.GetFields()
 | 
			
		||||
 | 
			
		||||
	labels["csm.remote_workload_type"] = getFromMetadata("type", fields)
 | 
			
		||||
	// The value of “csm.remote_workload_canonical_service” comes from
 | 
			
		||||
	// MetadataExchange with the key “canonical_service”. (Note that this should
 | 
			
		||||
	// be read even if the remote type is unknown.)
 | 
			
		||||
	labels["csm.remote_workload_canonical_service"] = getFromMetadata("canonical_service", fields)
 | 
			
		||||
 | 
			
		||||
	// Unset/unknown types, and types that aren't GKE or GCP return early with
 | 
			
		||||
	// just local labels, remote_workload_type and
 | 
			
		||||
	// remote_workload_canonical_service labels.
 | 
			
		||||
	workloadType := labels["csm.remote_workload_type"]
 | 
			
		||||
	if workloadType != "gcp_kubernetes_engine" && workloadType != "gcp_compute_engine" {
 | 
			
		||||
		return labels
 | 
			
		||||
	}
 | 
			
		||||
	// GKE and GCE labels.
 | 
			
		||||
	labels["csm.remote_workload_project_id"] = getFromMetadata("project_id", fields)
 | 
			
		||||
	labels["csm.remote_workload_location"] = getFromMetadata("location", fields)
 | 
			
		||||
	labels["csm.remote_workload_name"] = getFromMetadata("workload_name", fields)
 | 
			
		||||
	if workloadType == "gcp_compute_engine" {
 | 
			
		||||
		return labels
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// GKE only labels.
 | 
			
		||||
	labels["csm.remote_workload_cluster_name"] = getFromMetadata("cluster_name", fields)
 | 
			
		||||
	labels["csm.remote_workload_namespace_name"] = getFromMetadata("namespace_name", fields)
 | 
			
		||||
	return labels
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getFromMetadata gets the value for the metadata key from the protobuf
 | 
			
		||||
// metadata. Returns "unknown" if the metadata is not found in the protobuf
 | 
			
		||||
// metadata, or if the value is not a string value. Returns the string value
 | 
			
		||||
// otherwise.
 | 
			
		||||
func getFromMetadata(metadataKey string, metadata map[string]*structpb.Value) string {
 | 
			
		||||
	if metadata != nil {
 | 
			
		||||
		if metadataVal, ok := metadata[metadataKey]; ok {
 | 
			
		||||
			if _, ok := metadataVal.GetKind().(*structpb.Value_StringValue); ok {
 | 
			
		||||
				return metadataVal.GetStringValue()
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "unknown"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getFromResource gets the value for the resource key from the attribute set.
 | 
			
		||||
// Returns "unknown" if the resourceKey is not found in the attribute set or is
 | 
			
		||||
// not a string value, the string value otherwise.
 | 
			
		||||
func getFromResource(resourceKey attribute.Key, set *attribute.Set) string {
 | 
			
		||||
	if set != nil {
 | 
			
		||||
		if resourceVal, ok := set.Value(resourceKey); ok && resourceVal.Type() == attribute.STRING {
 | 
			
		||||
			return resourceVal.AsString()
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	return "unknown"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getEnv returns "unknown" if environment variable is unset, the environment
 | 
			
		||||
// variable otherwise.
 | 
			
		||||
func getEnv(name string) string {
 | 
			
		||||
	if val, ok := os.LookupEnv(name); ok {
 | 
			
		||||
		return val
 | 
			
		||||
	}
 | 
			
		||||
	return "unknown"
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	// This function will be overridden in unit tests.
 | 
			
		||||
	getAttrSetFromResourceDetector = func(ctx context.Context) *attribute.Set {
 | 
			
		||||
		r, err := resource.New(ctx, resource.WithDetectors(gcp.NewDetector()))
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			logger.Errorf("error reading OpenTelemetry resource: %v", err)
 | 
			
		||||
			return nil
 | 
			
		||||
		}
 | 
			
		||||
		return r.Set()
 | 
			
		||||
	}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// constructMetadataFromEnv creates local labels and labels to send to the peer
 | 
			
		||||
// using metadata exchange based off resource detection and environment
 | 
			
		||||
// variables.
 | 
			
		||||
//
 | 
			
		||||
// Returns local labels, and base 64 encoded protobuf.Struct containing metadata
 | 
			
		||||
// exchange labels.
 | 
			
		||||
func constructMetadataFromEnv(ctx context.Context) (map[string]string, string) {
 | 
			
		||||
	set := getAttrSetFromResourceDetector(ctx)
 | 
			
		||||
 | 
			
		||||
	labels := make(map[string]string)
 | 
			
		||||
	labels["type"] = getFromResource("cloud.platform", set)
 | 
			
		||||
	labels["canonical_service"] = getEnv("CSM_CANONICAL_SERVICE_NAME")
 | 
			
		||||
 | 
			
		||||
	// If type is not GCE or GKE only metadata exchange labels are "type" and
 | 
			
		||||
	// "canonical_service".
 | 
			
		||||
	cloudPlatformVal := labels["type"]
 | 
			
		||||
	if cloudPlatformVal != "gcp_kubernetes_engine" && cloudPlatformVal != "gcp_compute_engine" {
 | 
			
		||||
		return initializeLocalAndMetadataLabels(labels)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// GCE and GKE labels:
 | 
			
		||||
	labels["workload_name"] = getEnv("CSM_WORKLOAD_NAME")
 | 
			
		||||
 | 
			
		||||
	locationVal := "unknown"
 | 
			
		||||
	if resourceVal, ok := set.Value("cloud.availability_zone"); ok && resourceVal.Type() == attribute.STRING {
 | 
			
		||||
		locationVal = resourceVal.AsString()
 | 
			
		||||
	} else if resourceVal, ok = set.Value("cloud.region"); ok && resourceVal.Type() == attribute.STRING {
 | 
			
		||||
		locationVal = resourceVal.AsString()
 | 
			
		||||
	}
 | 
			
		||||
	labels["location"] = locationVal
 | 
			
		||||
 | 
			
		||||
	labels["project_id"] = getFromResource("cloud.account.id", set)
 | 
			
		||||
	if cloudPlatformVal == "gcp_compute_engine" {
 | 
			
		||||
		return initializeLocalAndMetadataLabels(labels)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// GKE specific labels:
 | 
			
		||||
	labels["namespace_name"] = getFromResource("k8s.namespace.name", set)
 | 
			
		||||
	labels["cluster_name"] = getFromResource("k8s.cluster.name", set)
 | 
			
		||||
	return initializeLocalAndMetadataLabels(labels)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// parseMeshIDString parses the mesh id from the node id according to the format
 | 
			
		||||
// "projects/[GCP Project number]/networks/mesh:[Mesh ID]/nodes/[UUID]". Returns
 | 
			
		||||
// "unknown" if there is a syntax error in the node ID.
 | 
			
		||||
func parseMeshIDFromNodeID(nodeID string) string {
 | 
			
		||||
	meshSplit := strings.Split(nodeID, "/")
 | 
			
		||||
	if len(meshSplit) != 6 {
 | 
			
		||||
		return "unknown"
 | 
			
		||||
	}
 | 
			
		||||
	if meshSplit[0] != "projects" || meshSplit[2] != "networks" || meshSplit[4] != "nodes" {
 | 
			
		||||
		return "unknown"
 | 
			
		||||
	}
 | 
			
		||||
	meshID, ok := strings.CutPrefix(meshSplit[3], "mesh:")
 | 
			
		||||
	if !ok { // errors become "unknown"
 | 
			
		||||
		return "unknown"
 | 
			
		||||
	}
 | 
			
		||||
	return meshID
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// initializeLocalAndMetadataLabels csm local labels for a CSM Plugin Option to
 | 
			
		||||
// record. It also builds out a base 64 encoded protobuf.Struct containing the
 | 
			
		||||
// metadata exchange labels to be sent as part of metadata exchange from a CSM
 | 
			
		||||
// Plugin Option.
 | 
			
		||||
func initializeLocalAndMetadataLabels(labels map[string]string) (map[string]string, string) {
 | 
			
		||||
	// The value of “csm.workload_canonical_service” comes from
 | 
			
		||||
	// “CSM_CANONICAL_SERVICE_NAME” env var, “unknown” if unset.
 | 
			
		||||
	val := labels["canonical_service"]
 | 
			
		||||
	localLabels := make(map[string]string)
 | 
			
		||||
	localLabels["csm.workload_canonical_service"] = val
 | 
			
		||||
	// Get the CSM Mesh ID from the bootstrap file.
 | 
			
		||||
	nodeID := getNodeID()
 | 
			
		||||
	localLabels["csm.mesh_id"] = parseMeshIDFromNodeID(nodeID)
 | 
			
		||||
 | 
			
		||||
	// Metadata exchange labels - can go ahead and encode into proto, and then
 | 
			
		||||
	// base64.
 | 
			
		||||
	pbLabels := &structpb.Struct{
 | 
			
		||||
		Fields: map[string]*structpb.Value{},
 | 
			
		||||
	}
 | 
			
		||||
	for k, v := range labels {
 | 
			
		||||
		pbLabels.Fields[k] = structpb.NewStringValue(v)
 | 
			
		||||
	}
 | 
			
		||||
	protoWireFormat, err := proto.Marshal(pbLabels)
 | 
			
		||||
	metadataExchangeLabelsEncoded := ""
 | 
			
		||||
	if err == nil {
 | 
			
		||||
		metadataExchangeLabelsEncoded = base64.RawStdEncoding.EncodeToString(protoWireFormat)
 | 
			
		||||
	}
 | 
			
		||||
	// else - This behavior triggers server side to reply (if sent from a gRPC
 | 
			
		||||
	// Client within this binary) with the metadata exchange labels. Even if
 | 
			
		||||
	// client side has a problem marshaling proto into wire format, it can
 | 
			
		||||
	// still use server labels so send an empty string as the value of
 | 
			
		||||
	// x-envoy-peer-metadata. The presence of this metadata exchange header
 | 
			
		||||
	// will cause server side to respond with metadata exchange labels.
 | 
			
		||||
 | 
			
		||||
	return localLabels, metadataExchangeLabelsEncoded
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// getNodeID gets the Node ID from the bootstrap data.
 | 
			
		||||
func getNodeID() string {
 | 
			
		||||
	cfg, err := bootstrap.NewConfig()
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return "" // will become "unknown"
 | 
			
		||||
	}
 | 
			
		||||
	if cfg.NodeProto == nil {
 | 
			
		||||
		return ""
 | 
			
		||||
	}
 | 
			
		||||
	return cfg.NodeProto.GetId()
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// metadataExchangeKey is the key for HTTP metadata exchange.
 | 
			
		||||
const metadataExchangeKey = "x-envoy-peer-metadata"
 | 
			
		||||
 | 
			
		||||
func determineTargetCSM(parsedTarget *url.URL) bool {
 | 
			
		||||
	// On the client-side, the channel target is used to determine if a channel is a
 | 
			
		||||
	// CSM channel or not. CSM channels need to have an “xds” scheme and a
 | 
			
		||||
	// "traffic-director-global.xds.googleapis.com" authority. In the cases where no
 | 
			
		||||
	// authority is mentioned, the authority is assumed to be CSM. MetadataExchange
 | 
			
		||||
	// is performed only for CSM channels. Non-metadata exchange labels are detected
 | 
			
		||||
	// as described below.
 | 
			
		||||
	return parsedTarget.Scheme == "xds" && (parsedTarget.Host == "" || parsedTarget.Host == "traffic-director-global.xds.googleapis.com")
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,548 @@
 | 
			
		|||
/*
 | 
			
		||||
 *
 | 
			
		||||
 * Copyright 2024 gRPC authors.
 | 
			
		||||
 *
 | 
			
		||||
 * 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 csm
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"os"
 | 
			
		||||
	"testing"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"google.golang.org/grpc/internal/envconfig"
 | 
			
		||||
	"google.golang.org/grpc/internal/grpctest"
 | 
			
		||||
	"google.golang.org/grpc/internal/testutils/xds/bootstrap"
 | 
			
		||||
	"google.golang.org/grpc/metadata"
 | 
			
		||||
 | 
			
		||||
	"github.com/google/go-cmp/cmp"
 | 
			
		||||
	"go.opentelemetry.io/otel/attribute"
 | 
			
		||||
 | 
			
		||||
	"google.golang.org/protobuf/proto"
 | 
			
		||||
	"google.golang.org/protobuf/types/known/structpb"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
type s struct {
 | 
			
		||||
	grpctest.Tester
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Test(t *testing.T) {
 | 
			
		||||
	grpctest.RunSubTests(t, s{})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
var defaultTestTimeout = 5 * time.Second
 | 
			
		||||
 | 
			
		||||
// clearEnv unsets all the environment variables relevant to the csm
 | 
			
		||||
// pluginOption.
 | 
			
		||||
func clearEnv() {
 | 
			
		||||
	os.Unsetenv(envconfig.XDSBootstrapFileContentEnv)
 | 
			
		||||
	os.Unsetenv(envconfig.XDSBootstrapFileNameEnv)
 | 
			
		||||
 | 
			
		||||
	os.Unsetenv("CSM_CANONICAL_SERVICE_NAME")
 | 
			
		||||
	os.Unsetenv("CSM_WORKLOAD_NAME")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s) TestGetLabels(t *testing.T) {
 | 
			
		||||
	clearEnv()
 | 
			
		||||
	ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
 | 
			
		||||
	defer cancel()
 | 
			
		||||
	cpo := newPluginOption(ctx)
 | 
			
		||||
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name                   string
 | 
			
		||||
		unsetHeader            bool // Should trigger "unknown" labels
 | 
			
		||||
		twoValues              bool // Should trigger "unknown" labels
 | 
			
		||||
		metadataExchangeLabels map[string]string
 | 
			
		||||
		labelsWant             map[string]string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:                   "unset-labels",
 | 
			
		||||
			metadataExchangeLabels: nil,
 | 
			
		||||
			labelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
 | 
			
		||||
				"csm.remote_workload_type":              "unknown",
 | 
			
		||||
				"csm.remote_workload_canonical_service": "unknown",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "metadata-partially-set",
 | 
			
		||||
			metadataExchangeLabels: map[string]string{
 | 
			
		||||
				"type":        "not-gce-or-gke",
 | 
			
		||||
				"ignore-this": "ignore-this",
 | 
			
		||||
			},
 | 
			
		||||
			labelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
 | 
			
		||||
				"csm.remote_workload_type":              "not-gce-or-gke",
 | 
			
		||||
				"csm.remote_workload_canonical_service": "unknown",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "google-compute-engine",
 | 
			
		||||
			metadataExchangeLabels: map[string]string{ // All of these labels get emitted when type is "gcp_compute_engine".
 | 
			
		||||
				"type":              "gcp_compute_engine",
 | 
			
		||||
				"canonical_service": "canonical_service_val",
 | 
			
		||||
				"project_id":        "unique-id",
 | 
			
		||||
				"location":          "us-east",
 | 
			
		||||
				"workload_name":     "workload_name_val",
 | 
			
		||||
			},
 | 
			
		||||
			labelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
 | 
			
		||||
				"csm.remote_workload_type":              "gcp_compute_engine",
 | 
			
		||||
				"csm.remote_workload_canonical_service": "canonical_service_val",
 | 
			
		||||
				"csm.remote_workload_project_id":        "unique-id",
 | 
			
		||||
				"csm.remote_workload_location":          "us-east",
 | 
			
		||||
				"csm.remote_workload_name":              "workload_name_val",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		// unset should go to unknown, ignore GKE labels that are not relevant
 | 
			
		||||
		// to GCE.
 | 
			
		||||
		{
 | 
			
		||||
			name: "google-compute-engine-labels-partially-set-with-extra",
 | 
			
		||||
			metadataExchangeLabels: map[string]string{
 | 
			
		||||
				"type":              "gcp_compute_engine",
 | 
			
		||||
				"canonical_service": "canonical_service_val",
 | 
			
		||||
				"project_id":        "unique-id",
 | 
			
		||||
				"location":          "us-east",
 | 
			
		||||
				// "workload_name": "", unset workload name - should become "unknown"
 | 
			
		||||
				"namespace_name": "should-be-ignored",
 | 
			
		||||
				"cluster_name":   "should-be-ignored",
 | 
			
		||||
			},
 | 
			
		||||
			labelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
 | 
			
		||||
				"csm.remote_workload_type":              "gcp_compute_engine",
 | 
			
		||||
				"csm.remote_workload_canonical_service": "canonical_service_val",
 | 
			
		||||
				"csm.remote_workload_project_id":        "unique-id",
 | 
			
		||||
				"csm.remote_workload_location":          "us-east",
 | 
			
		||||
				"csm.remote_workload_name":              "unknown",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "google-kubernetes-engine",
 | 
			
		||||
			metadataExchangeLabels: map[string]string{
 | 
			
		||||
				"type":              "gcp_kubernetes_engine",
 | 
			
		||||
				"canonical_service": "canonical_service_val",
 | 
			
		||||
				"project_id":        "unique-id",
 | 
			
		||||
				"namespace_name":    "namespace_name_val",
 | 
			
		||||
				"cluster_name":      "cluster_name_val",
 | 
			
		||||
				"location":          "us-east",
 | 
			
		||||
				"workload_name":     "workload_name_val",
 | 
			
		||||
			},
 | 
			
		||||
			labelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
 | 
			
		||||
				"csm.remote_workload_type":              "gcp_kubernetes_engine",
 | 
			
		||||
				"csm.remote_workload_canonical_service": "canonical_service_val",
 | 
			
		||||
				"csm.remote_workload_project_id":        "unique-id",
 | 
			
		||||
				"csm.remote_workload_cluster_name":      "cluster_name_val",
 | 
			
		||||
				"csm.remote_workload_namespace_name":    "namespace_name_val",
 | 
			
		||||
				"csm.remote_workload_location":          "us-east",
 | 
			
		||||
				"csm.remote_workload_name":              "workload_name_val",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "google-kubernetes-engine-labels-partially-set",
 | 
			
		||||
			metadataExchangeLabels: map[string]string{
 | 
			
		||||
				"type":              "gcp_kubernetes_engine",
 | 
			
		||||
				"canonical_service": "canonical_service_val",
 | 
			
		||||
				"project_id":        "unique-id",
 | 
			
		||||
				"namespace_name":    "namespace_name_val",
 | 
			
		||||
				// "cluster_name": "", cluster_name unset, should become "unknown"
 | 
			
		||||
				"location": "us-east",
 | 
			
		||||
				// "workload_name": "", workload_name unset, should become "unknown"
 | 
			
		||||
			},
 | 
			
		||||
			labelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
 | 
			
		||||
				"csm.remote_workload_type":              "gcp_kubernetes_engine",
 | 
			
		||||
				"csm.remote_workload_canonical_service": "canonical_service_val",
 | 
			
		||||
				"csm.remote_workload_project_id":        "unique-id",
 | 
			
		||||
				"csm.remote_workload_cluster_name":      "unknown",
 | 
			
		||||
				"csm.remote_workload_namespace_name":    "namespace_name_val",
 | 
			
		||||
				"csm.remote_workload_location":          "us-east",
 | 
			
		||||
				"csm.remote_workload_name":              "unknown",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "unset-header",
 | 
			
		||||
			metadataExchangeLabels: map[string]string{
 | 
			
		||||
				"type":              "gcp_kubernetes_engine",
 | 
			
		||||
				"canonical_service": "canonical_service_val",
 | 
			
		||||
				"project_id":        "unique-id",
 | 
			
		||||
				"namespace_name":    "namespace_name_val",
 | 
			
		||||
				"cluster_name":      "cluster_name_val",
 | 
			
		||||
				"location":          "us-east",
 | 
			
		||||
				"workload_name":     "workload_name_val",
 | 
			
		||||
			},
 | 
			
		||||
			unsetHeader: true,
 | 
			
		||||
			labelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
 | 
			
		||||
				"csm.remote_workload_type":              "unknown",
 | 
			
		||||
				"csm.remote_workload_canonical_service": "unknown",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "two-header-values",
 | 
			
		||||
			metadataExchangeLabels: map[string]string{
 | 
			
		||||
				"type":              "gcp_kubernetes_engine",
 | 
			
		||||
				"canonical_service": "canonical_service_val",
 | 
			
		||||
				"project_id":        "unique-id",
 | 
			
		||||
				"namespace_name":    "namespace_name_val",
 | 
			
		||||
				"cluster_name":      "cluster_name_val",
 | 
			
		||||
				"location":          "us-east",
 | 
			
		||||
				"workload_name":     "workload_name_val",
 | 
			
		||||
			},
 | 
			
		||||
			twoValues: true,
 | 
			
		||||
			labelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
 | 
			
		||||
				"csm.remote_workload_type":              "unknown",
 | 
			
		||||
				"csm.remote_workload_canonical_service": "unknown",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		t.Run(test.name, func(t *testing.T) {
 | 
			
		||||
			pbLabels := &structpb.Struct{
 | 
			
		||||
				Fields: map[string]*structpb.Value{},
 | 
			
		||||
			}
 | 
			
		||||
			for k, v := range test.metadataExchangeLabels {
 | 
			
		||||
				pbLabels.Fields[k] = structpb.NewStringValue(v)
 | 
			
		||||
			}
 | 
			
		||||
			protoWireFormat, err := proto.Marshal(pbLabels)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("Error marshaling proto: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			metadataExchangeLabelsEncoded := base64.RawStdEncoding.EncodeToString(protoWireFormat)
 | 
			
		||||
			md := metadata.New(map[string]string{
 | 
			
		||||
				metadataExchangeKey: metadataExchangeLabelsEncoded,
 | 
			
		||||
			})
 | 
			
		||||
			if test.unsetHeader {
 | 
			
		||||
				md.Delete(metadataExchangeKey)
 | 
			
		||||
			}
 | 
			
		||||
			if test.twoValues {
 | 
			
		||||
				md.Append(metadataExchangeKey, "extra-val")
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			labelsGot := cpo.GetLabels(md)
 | 
			
		||||
			if diff := cmp.Diff(labelsGot, test.labelsWant); diff != "" {
 | 
			
		||||
				t.Fatalf("cpo.GetLabels returned unexpected value (-got, +want): %v", diff)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestDetermineTargetCSM tests the helper function that determines whether a
 | 
			
		||||
// target is relevant to CSM or not, based off the rules outlined in design.
 | 
			
		||||
func (s) TestDetermineTargetCSM(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name      string
 | 
			
		||||
		target    string
 | 
			
		||||
		targetCSM bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:      "dns:///localhost",
 | 
			
		||||
			target:    "normal-target-here",
 | 
			
		||||
			targetCSM: false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "xds-no-authority",
 | 
			
		||||
			target:    "xds:///localhost",
 | 
			
		||||
			targetCSM: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "xds-traffic-director-authority",
 | 
			
		||||
			target:    "xds://traffic-director-global.xds.googleapis.com/localhost",
 | 
			
		||||
			targetCSM: true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "xds-not-traffic-director-authority",
 | 
			
		||||
			target:    "xds://not-traffic-director-authority/localhost",
 | 
			
		||||
			targetCSM: false,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		t.Run(test.name, func(t *testing.T) {
 | 
			
		||||
			parsedTarget, err := url.Parse(test.target)
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("test target %v failed to parse: %v", test.target, err)
 | 
			
		||||
			}
 | 
			
		||||
			if got := determineTargetCSM(parsedTarget); got != test.targetCSM {
 | 
			
		||||
				t.Fatalf("cpo.determineTargetCSM(%v): got %v, want %v", test.target, got, test.targetCSM)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func (s) TestBootstrap(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name       string
 | 
			
		||||
		nodeID     string
 | 
			
		||||
		meshIDWant string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:       "malformed-node-id-unknown",
 | 
			
		||||
			nodeID:     "malformed",
 | 
			
		||||
			meshIDWant: "unknown",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:       "node-id-parsed",
 | 
			
		||||
			nodeID:     "projects/12345/networks/mesh:mesh_id/nodes/aaaa-aaaa-aaaa-aaaa",
 | 
			
		||||
			meshIDWant: "mesh_id",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:       "wrong-syntax-unknown",
 | 
			
		||||
			nodeID:     "wrong-syntax/12345/networks/mesh:mesh_id/nodes/aaaa-aaaa-aaaa-aaaa",
 | 
			
		||||
			meshIDWant: "unknown",
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:       "node-id-parsed",
 | 
			
		||||
			nodeID:     "projects/12345/networks/mesh:/nodes/aaaa-aaaa-aaaa-aaaa",
 | 
			
		||||
			meshIDWant: "",
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		t.Run(test.name, func(t *testing.T) {
 | 
			
		||||
			cleanup, err := bootstrap.CreateFile(bootstrap.Options{
 | 
			
		||||
				NodeID:    test.nodeID,
 | 
			
		||||
				ServerURI: "xds_server_uri",
 | 
			
		||||
			})
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				t.Fatalf("failed to create bootstrap: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			defer cleanup()
 | 
			
		||||
			nodeIDGot := getNodeID() // this should return the node ID plumbed into bootstrap above
 | 
			
		||||
			if nodeIDGot != test.nodeID {
 | 
			
		||||
				t.Fatalf("getNodeID: got %v, want %v", nodeIDGot, test.nodeID)
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			meshIDGot := parseMeshIDFromNodeID(nodeIDGot)
 | 
			
		||||
			if meshIDGot != test.meshIDWant {
 | 
			
		||||
				t.Fatalf("parseMeshIDFromNodeID(%v): got %v, want %v", nodeIDGot, meshIDGot, test.meshIDWant)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// TestSetLabels tests the setting of labels, which snapshots the resource and
 | 
			
		||||
// environment. It mocks the resource and environment, and then calls into
 | 
			
		||||
// labels creation. It verifies to local labels created and metadata exchange
 | 
			
		||||
// labels emitted from the setLabels function.
 | 
			
		||||
func (s) TestSetLabels(t *testing.T) {
 | 
			
		||||
	clearEnv()
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name                             string
 | 
			
		||||
		resourceKeyValues                map[string]string
 | 
			
		||||
		csmCanonicalServiceNamePopulated bool
 | 
			
		||||
		csmWorkloadNamePopulated         bool
 | 
			
		||||
		bootstrapGeneratorPopulated      bool
 | 
			
		||||
		localLabelsWant                  map[string]string
 | 
			
		||||
		metadataExchangeLabelsWant       map[string]string
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:                             "no-type",
 | 
			
		||||
			csmCanonicalServiceNamePopulated: true,
 | 
			
		||||
			bootstrapGeneratorPopulated:      true,
 | 
			
		||||
			resourceKeyValues:                map[string]string{},
 | 
			
		||||
			localLabelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "canonical_service_name_val", // env var populated so should be set.
 | 
			
		||||
				"csm.mesh_id":                    "mesh_id",                    // env var populated so should be set.
 | 
			
		||||
			},
 | 
			
		||||
			metadataExchangeLabelsWant: map[string]string{
 | 
			
		||||
				"type":              "unknown",
 | 
			
		||||
				"canonical_service": "canonical_service_name_val", // env var populated so should be set.
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:                     "gce",
 | 
			
		||||
			csmWorkloadNamePopulated: true,
 | 
			
		||||
			resourceKeyValues: map[string]string{
 | 
			
		||||
				"cloud.platform": "gcp_compute_engine",
 | 
			
		||||
				// csm workload name is an env var
 | 
			
		||||
				"cloud.availability_zone": "cloud_availability_zone_val",
 | 
			
		||||
				"cloud.region":            "should-be-ignored", // cloud.availability_zone takes precedence
 | 
			
		||||
				"cloud.account.id":        "cloud_account_id_val",
 | 
			
		||||
			},
 | 
			
		||||
			localLabelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
			},
 | 
			
		||||
			metadataExchangeLabelsWant: map[string]string{
 | 
			
		||||
				"type":              "gcp_compute_engine",
 | 
			
		||||
				"canonical_service": "unknown",
 | 
			
		||||
				"workload_name":     "workload_name_val",
 | 
			
		||||
				"location":          "cloud_availability_zone_val",
 | 
			
		||||
				"project_id":        "cloud_account_id_val",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "gce-half-unset",
 | 
			
		||||
			resourceKeyValues: map[string]string{
 | 
			
		||||
				"cloud.platform": "gcp_compute_engine",
 | 
			
		||||
				// csm workload name is an env var
 | 
			
		||||
				"cloud.availability_zone": "cloud_availability_zone_val",
 | 
			
		||||
				"cloud.region":            "should-be-ignored", // cloud.availability_zone takes precedence
 | 
			
		||||
			},
 | 
			
		||||
			localLabelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
			},
 | 
			
		||||
			metadataExchangeLabelsWant: map[string]string{
 | 
			
		||||
				"type":              "gcp_compute_engine",
 | 
			
		||||
				"canonical_service": "unknown",
 | 
			
		||||
				"workload_name":     "unknown",
 | 
			
		||||
				"location":          "cloud_availability_zone_val",
 | 
			
		||||
				"project_id":        "unknown",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "gke",
 | 
			
		||||
			resourceKeyValues: map[string]string{
 | 
			
		||||
				"cloud.platform": "gcp_kubernetes_engine",
 | 
			
		||||
				// csm workload name is an env var
 | 
			
		||||
				"cloud.region":       "cloud_region_val", // availability_zone isn't present, so this should become location
 | 
			
		||||
				"cloud.account.id":   "cloud_account_id_val",
 | 
			
		||||
				"k8s.namespace.name": "k8s_namespace_name_val",
 | 
			
		||||
				"k8s.cluster.name":   "k8s_cluster_name_val",
 | 
			
		||||
			},
 | 
			
		||||
			localLabelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
			},
 | 
			
		||||
			metadataExchangeLabelsWant: map[string]string{
 | 
			
		||||
				"type":              "gcp_kubernetes_engine",
 | 
			
		||||
				"canonical_service": "unknown",
 | 
			
		||||
				"workload_name":     "unknown",
 | 
			
		||||
				"location":          "cloud_region_val",
 | 
			
		||||
				"project_id":        "cloud_account_id_val",
 | 
			
		||||
				"namespace_name":    "k8s_namespace_name_val",
 | 
			
		||||
				"cluster_name":      "k8s_cluster_name_val",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name: "gke-half-unset",
 | 
			
		||||
			resourceKeyValues: map[string]string{ // unset should become unknown
 | 
			
		||||
				"cloud.platform": "gcp_kubernetes_engine",
 | 
			
		||||
				// csm workload name is an env var
 | 
			
		||||
				"cloud.region": "cloud_region_val", // availability_zone isn't present, so this should become location
 | 
			
		||||
				// "cloud.account.id": "", // unset - should become unknown
 | 
			
		||||
				"k8s.namespace.name": "k8s_namespace_name_val",
 | 
			
		||||
				// "k8s.cluster.name": "", // unset - should become unknown
 | 
			
		||||
			},
 | 
			
		||||
			localLabelsWant: map[string]string{
 | 
			
		||||
				"csm.workload_canonical_service": "unknown",
 | 
			
		||||
				"csm.mesh_id":                    "unknown",
 | 
			
		||||
			},
 | 
			
		||||
			metadataExchangeLabelsWant: map[string]string{
 | 
			
		||||
				"type":              "gcp_kubernetes_engine",
 | 
			
		||||
				"canonical_service": "unknown",
 | 
			
		||||
				"workload_name":     "unknown",
 | 
			
		||||
				"location":          "cloud_region_val",
 | 
			
		||||
				"project_id":        "unknown",
 | 
			
		||||
				"namespace_name":    "k8s_namespace_name_val",
 | 
			
		||||
				"cluster_name":      "unknown",
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	for _, test := range tests {
 | 
			
		||||
		t.Run(test.name, func(t *testing.T) {
 | 
			
		||||
			func() {
 | 
			
		||||
				if test.csmCanonicalServiceNamePopulated {
 | 
			
		||||
					os.Setenv("CSM_CANONICAL_SERVICE_NAME", "canonical_service_name_val")
 | 
			
		||||
					defer os.Unsetenv("CSM_CANONICAL_SERVICE_NAME")
 | 
			
		||||
				}
 | 
			
		||||
				if test.csmWorkloadNamePopulated {
 | 
			
		||||
					os.Setenv("CSM_WORKLOAD_NAME", "workload_name_val")
 | 
			
		||||
					defer os.Unsetenv("CSM_WORKLOAD_NAME")
 | 
			
		||||
				}
 | 
			
		||||
				if test.bootstrapGeneratorPopulated {
 | 
			
		||||
					cleanup, err := bootstrap.CreateFile(bootstrap.Options{
 | 
			
		||||
						NodeID:    "projects/12345/networks/mesh:mesh_id/nodes/aaaa-aaaa-aaaa-aaaa",
 | 
			
		||||
						ServerURI: "xds_server_uri",
 | 
			
		||||
					})
 | 
			
		||||
					if err != nil {
 | 
			
		||||
						t.Fatalf("failed to create bootstrap: %v", err)
 | 
			
		||||
					}
 | 
			
		||||
					defer cleanup()
 | 
			
		||||
				}
 | 
			
		||||
				var attributes []attribute.KeyValue
 | 
			
		||||
				for k, v := range test.resourceKeyValues {
 | 
			
		||||
					attributes = append(attributes, attribute.String(k, v))
 | 
			
		||||
				}
 | 
			
		||||
				// Return the attributes configured as part of the test in place
 | 
			
		||||
				// of reading from resource.
 | 
			
		||||
				attrSet := attribute.NewSet(attributes...)
 | 
			
		||||
				origGetAttrSet := getAttrSetFromResourceDetector
 | 
			
		||||
				getAttrSetFromResourceDetector = func(context.Context) *attribute.Set {
 | 
			
		||||
					return &attrSet
 | 
			
		||||
				}
 | 
			
		||||
				defer func() { getAttrSetFromResourceDetector = origGetAttrSet }()
 | 
			
		||||
 | 
			
		||||
				ctx, cancel := context.WithTimeout(context.Background(), defaultTestTimeout)
 | 
			
		||||
				defer cancel()
 | 
			
		||||
				localLabelsGot, mdEncoded := constructMetadataFromEnv(ctx)
 | 
			
		||||
				if diff := cmp.Diff(localLabelsGot, test.localLabelsWant); diff != "" {
 | 
			
		||||
					t.Fatalf("constructMetadataFromEnv() want: %v, got %v", test.localLabelsWant, localLabelsGot)
 | 
			
		||||
				}
 | 
			
		||||
 | 
			
		||||
				verifyMetadataExchangeLabels(mdEncoded, test.metadataExchangeLabelsWant)
 | 
			
		||||
			}()
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func verifyMetadataExchangeLabels(mdEncoded string, mdLabelsWant map[string]string) error {
 | 
			
		||||
	protoWireFormat, err := base64.RawStdEncoding.DecodeString(mdEncoded)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return fmt.Errorf("error base 64 decoding metadata val: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	spb := &structpb.Struct{}
 | 
			
		||||
	if err := proto.Unmarshal(protoWireFormat, spb); err != nil {
 | 
			
		||||
		return fmt.Errorf("error unmarshaling proto wire format: %v", err)
 | 
			
		||||
	}
 | 
			
		||||
	fields := spb.GetFields()
 | 
			
		||||
	for k, v := range mdLabelsWant {
 | 
			
		||||
		if val, ok := fields[k]; !ok {
 | 
			
		||||
			if _, ok := val.GetKind().(*structpb.Value_StringValue); !ok {
 | 
			
		||||
				return fmt.Errorf("struct value for key %v should be string type", k)
 | 
			
		||||
			}
 | 
			
		||||
			if val.GetStringValue() != v {
 | 
			
		||||
				return fmt.Errorf("struct value for key %v got: %v, want %v", k, val.GetStringValue(), v)
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	if len(mdLabelsWant) != len(fields) {
 | 
			
		||||
		return fmt.Errorf("len(mdLabelsWant) = %v, len(mdLabelsGot) = %v", len(mdLabelsWant), len(fields))
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,24 +1,35 @@
 | 
			
		|||
module google.golang.org/grpc/stats/opentelemetry
 | 
			
		||||
 | 
			
		||||
go 1.20
 | 
			
		||||
go 1.21
 | 
			
		||||
 | 
			
		||||
replace google.golang.org/grpc => ../..
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	go.opentelemetry.io/otel v1.24.0
 | 
			
		||||
	go.opentelemetry.io/otel/metric v1.24.0
 | 
			
		||||
	github.com/google/go-cmp v0.6.0
 | 
			
		||||
	go.opentelemetry.io/contrib/detectors/gcp v1.26.0
 | 
			
		||||
	go.opentelemetry.io/otel v1.26.0
 | 
			
		||||
	go.opentelemetry.io/otel/metric v1.26.0
 | 
			
		||||
	go.opentelemetry.io/otel/sdk v1.26.0
 | 
			
		||||
	go.opentelemetry.io/otel/sdk/metric v1.24.0
 | 
			
		||||
	google.golang.org/grpc v1.62.1
 | 
			
		||||
	google.golang.org/protobuf v1.33.0
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	cloud.google.com/go/compute/metadata v0.3.0 // indirect
 | 
			
		||||
	github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.22.0 // indirect
 | 
			
		||||
	github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 // indirect
 | 
			
		||||
	github.com/envoyproxy/go-control-plane v0.12.0 // indirect
 | 
			
		||||
	github.com/envoyproxy/protoc-gen-validate v1.0.4 // indirect
 | 
			
		||||
	github.com/go-logr/logr v1.4.1 // indirect
 | 
			
		||||
	github.com/go-logr/stdr v1.2.2 // indirect
 | 
			
		||||
	go.opentelemetry.io/otel/sdk v1.24.0 // indirect
 | 
			
		||||
	go.opentelemetry.io/otel/trace v1.24.0 // indirect
 | 
			
		||||
	github.com/golang/protobuf v1.5.4 // indirect
 | 
			
		||||
	go.opentelemetry.io/otel/trace v1.26.0 // indirect
 | 
			
		||||
	golang.org/x/net v0.22.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.18.0 // indirect
 | 
			
		||||
	golang.org/x/oauth2 v0.18.0 // indirect
 | 
			
		||||
	golang.org/x/sync v0.6.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.19.0 // indirect
 | 
			
		||||
	golang.org/x/text v0.14.0 // indirect
 | 
			
		||||
	google.golang.org/appengine v1.6.8 // indirect
 | 
			
		||||
	google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 // indirect
 | 
			
		||||
	google.golang.org/protobuf v1.33.0 // indirect
 | 
			
		||||
)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,30 +1,92 @@
 | 
			
		|||
cloud.google.com/go/compute/metadata v0.3.0 h1:Tz+eQXMEqDIKRsmY3cHTL6FVaynIjX2QxYC4trgAKZc=
 | 
			
		||||
cloud.google.com/go/compute/metadata v0.3.0/go.mod h1:zFmK7XCadkQkj6TtorcaGlCW1hT1fIilQDwofLpJ20k=
 | 
			
		||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.22.0 h1:PWcDbDjrcT/ZHLn4Bc/FuglaZZVPP8bWO/YRmJBbe38=
 | 
			
		||||
github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.22.0/go.mod h1:XEK/YHYsi+Wk2Bk1+zi/he+gjRfDWtoIZEZwuwcYjhk=
 | 
			
		||||
github.com/census-instrumentation/opencensus-proto v0.4.1 h1:iKLQ0xPNFxR/2hzXZMrBo8f1j86j5WHzznCCQxV/b8g=
 | 
			
		||||
github.com/census-instrumentation/opencensus-proto v0.4.1/go.mod h1:4T9NM4+4Vw91VeyqjLS6ao50K5bOcLKN6Q42XnYaRYw=
 | 
			
		||||
github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50 h1:DBmgJDC9dTfkVyGgipamEh2BpGYxScCH1TOF1LL1cXc=
 | 
			
		||||
github.com/cncf/xds/go v0.0.0-20240318125728-8a4994d93e50/go.mod h1:5e1+Vvlzido69INQaVO6d87Qn543Xr6nooe9Kz7oBFM=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
			
		||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.12.0 h1:4X+VP1GHd1Mhj6IB5mMeGbLCleqxjletLK6K0rbxyZI=
 | 
			
		||||
github.com/envoyproxy/go-control-plane v0.12.0/go.mod h1:ZBTaoJ23lqITozF0M6G4/IragXCQKCnYbmlmtHvwRG0=
 | 
			
		||||
github.com/envoyproxy/protoc-gen-validate v1.0.4 h1:gVPz/FMfvh57HdSJQyvBtF00j8JU4zdyUgIUNhlgg0A=
 | 
			
		||||
github.com/envoyproxy/protoc-gen-validate v1.0.4/go.mod h1:qys6tmnRsYrQqIhm2bvKZH4Blx/1gTIZ2UKVY1M+Yew=
 | 
			
		||||
github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
 | 
			
		||||
github.com/go-logr/logr v1.4.1 h1:pKouT5E8xu9zeFC39JXRDukb6JFQPXM5p5I91188VAQ=
 | 
			
		||||
github.com/go-logr/logr v1.4.1/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
 | 
			
		||||
github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag=
 | 
			
		||||
github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE=
 | 
			
		||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
 | 
			
		||||
github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY=
 | 
			
		||||
github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek=
 | 
			
		||||
github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps=
 | 
			
		||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
 | 
			
		||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
 | 
			
		||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
			
		||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
			
		||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
 | 
			
		||||
go.opentelemetry.io/otel v1.24.0 h1:0LAOdjNmQeSTzGBzduGe/rU4tZhMwL5rWgtp9Ku5Jfo=
 | 
			
		||||
go.opentelemetry.io/otel v1.24.0/go.mod h1:W7b9Ozg4nkF5tWI5zsXkaKKDjdVjpD4oAt9Qi/MArHo=
 | 
			
		||||
go.opentelemetry.io/otel/metric v1.24.0 h1:6EhoGWWK28x1fbpA4tYTOWBkPefTDQnb8WSGXlc88kI=
 | 
			
		||||
go.opentelemetry.io/otel/metric v1.24.0/go.mod h1:VYhLe1rFfxuTXLgj4CBiyz+9WYBA8pNGJgDcSFRKBco=
 | 
			
		||||
go.opentelemetry.io/otel/sdk v1.24.0 h1:YMPPDNymmQN3ZgczicBY3B6sf9n62Dlj9pWD3ucgoDw=
 | 
			
		||||
go.opentelemetry.io/otel/sdk v1.24.0/go.mod h1:KVrIYw6tEubO9E96HQpcmpTKDVn9gdv35HoYiQWGDFg=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
			
		||||
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
 | 
			
		||||
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
			
		||||
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
 | 
			
		||||
go.opentelemetry.io/contrib/detectors/gcp v1.26.0 h1:NeL09fPJQ4C350Q4TAoBCfLMeUmwbsRdrWAIQhPLKtA=
 | 
			
		||||
go.opentelemetry.io/contrib/detectors/gcp v1.26.0/go.mod h1:F5oCMdHOftNwFDC0lUS+c5q6tvyb8C/hwdL9yP+AkXk=
 | 
			
		||||
go.opentelemetry.io/otel v1.26.0 h1:LQwgL5s/1W7YiiRwxf03QGnWLb2HW4pLiAhaA5cZXBs=
 | 
			
		||||
go.opentelemetry.io/otel v1.26.0/go.mod h1:UmLkJHUAidDval2EICqBMbnAd0/m2vmpf/dAM+fvFs4=
 | 
			
		||||
go.opentelemetry.io/otel/metric v1.26.0 h1:7S39CLuY5Jgg9CrnA9HHiEjGMF/X2VHvoXGgSllRz30=
 | 
			
		||||
go.opentelemetry.io/otel/metric v1.26.0/go.mod h1:SY+rHOI4cEawI9a7N1A4nIg/nTQXe1ccCNWYOJUrpX4=
 | 
			
		||||
go.opentelemetry.io/otel/sdk v1.26.0 h1:Y7bumHf5tAiDlRYFmGqetNcLaVUZmh4iYfmGxtmz7F8=
 | 
			
		||||
go.opentelemetry.io/otel/sdk v1.26.0/go.mod h1:0p8MXpqLeJ0pzcszQQN4F0S5FVjBLgypeGSngLsmirs=
 | 
			
		||||
go.opentelemetry.io/otel/sdk/metric v1.24.0 h1:yyMQrPzF+k88/DbH7o4FMAs80puqd+9osbiBrJrz/w8=
 | 
			
		||||
go.opentelemetry.io/otel/sdk/metric v1.24.0/go.mod h1:I6Y5FjH6rvEnTTAYQz3Mmv2kl6Ek5IIrmwTLqMrrOE0=
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.24.0 h1:CsKnnL4dUAr/0llH9FKuc698G04IrpWV0MQA/Y1YELI=
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.24.0/go.mod h1:HPc3Xr/cOApsBI154IU0OI0HJexz+aw5uPdbs3UCjNU=
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.26.0 h1:1ieeAUb4y0TE26jUFrCIXKpTuVK7uJGN9/Z/2LP5sQA=
 | 
			
		||||
go.opentelemetry.io/otel/trace v1.26.0/go.mod h1:4iDxvGDQuUkHve82hJJ8UqrwswHYsZuWCBllGV2U2y0=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 | 
			
		||||
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
 | 
			
		||||
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
 | 
			
		||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
			
		||||
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
 | 
			
		||||
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
 | 
			
		||||
golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
 | 
			
		||||
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
 | 
			
		||||
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/oauth2 v0.18.0 h1:09qnuIAgzdx1XplqJvW6CQqMCtGZykZWcXzPMPUusvI=
 | 
			
		||||
golang.org/x/oauth2 v0.18.0/go.mod h1:Wf7knwG0MPoWIMMBgFlEaSUDaKskp0dCfrlJRJXbBi8=
 | 
			
		||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 | 
			
		||||
golang.org/x/sync v0.6.0 h1:5BMeUDZ7vkXGfEr1x9B4bRcTH4lpkTkpdh0T/J+qjbQ=
 | 
			
		||||
golang.org/x/sync v0.6.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
 | 
			
		||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
			
		||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 | 
			
		||||
golang.org/x/sys v0.19.0 h1:q5f1RH2jigJ1MoAWp2KTp3gm5zAGFUTarQZ5U386+4o=
 | 
			
		||||
golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
			
		||||
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
 | 
			
		||||
golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ=
 | 
			
		||||
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
 | 
			
		||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
 | 
			
		||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
			
		||||
golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
 | 
			
		||||
golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
 | 
			
		||||
google.golang.org/appengine v1.6.8 h1:IhEN5q69dyKagZPYMSdIjS2HqprW324FRQZJcGqPAsM=
 | 
			
		||||
google.golang.org/appengine v1.6.8/go.mod h1:1jJ3jBArFh5pcgW8gCtRJnepW8FzD1V44FJffLiz/Ds=
 | 
			
		||||
google.golang.org/genproto v0.0.0-20230711160842-782d3b101e98 h1:Z0hjGZePRE0ZBWotvtrwxFNrNE9CUAGtplaDK5NNI/g=
 | 
			
		||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237 h1:RFiFrvy37/mpSpdySBDrUdipW/dHwsRwh3J3+A9VgT4=
 | 
			
		||||
google.golang.org/genproto/googleapis/api v0.0.0-20240318140521-94a12d6c2237/go.mod h1:Z5Iiy3jtmioajWHDGFk7CeugTyHtPvMHA4UTmUkyalE=
 | 
			
		||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237 h1:NnYq6UN9ReLM9/Y01KWNOWyI5xQ9kbIms5GGJVwS/Yc=
 | 
			
		||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20240318140521-94a12d6c2237/go.mod h1:WtryC6hu0hhx87FDGxWCDptyssuo68sk10vYjF+T9fY=
 | 
			
		||||
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
 | 
			
		||||
google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc=
 | 
			
		||||
google.golang.org/protobuf v1.33.0 h1:uNO2rsAINq/JlFpSdYEKIZ0uKD/R9cpdv0T+yoGwGmI=
 | 
			
		||||
google.golang.org/protobuf v1.33.0/go.mod h1:c6P6GXX6sHbq/GpV6MGZEdwhWPcYBgnhAHhKbcUYpos=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,38 @@
 | 
			
		|||
/*
 | 
			
		||||
 * Copyright 2024 gRPC authors.
 | 
			
		||||
 *
 | 
			
		||||
 * 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 internal defines the PluginOption interface.
 | 
			
		||||
package internal
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"google.golang.org/grpc/metadata"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// PluginOption is the interface which represents a plugin option for the
 | 
			
		||||
// OpenTelemetry instrumentation component. This plugin option emits labels from
 | 
			
		||||
// metadata and also creates metadata containing labels. These labels are
 | 
			
		||||
// intended to be added to applicable OpenTelemetry metrics recorded in the
 | 
			
		||||
// OpenTelemetry instrumentation component.
 | 
			
		||||
//
 | 
			
		||||
// In the future, we hope to stabilize and expose this API to allow plugins to
 | 
			
		||||
// inject labels of their choosing into metrics recorded.
 | 
			
		||||
type PluginOption interface {
 | 
			
		||||
	// GetMetadata creates a MD with metadata exchange labels.
 | 
			
		||||
	GetMetadata() metadata.MD
 | 
			
		||||
	// GetLabels emits labels to be attached to metrics for the RPC that
 | 
			
		||||
	// contains the provided incomingMetadata.
 | 
			
		||||
	GetLabels(incomingMetadata metadata.MD) map[string]string
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -37,7 +37,9 @@ import (
 | 
			
		|||
)
 | 
			
		||||
 | 
			
		||||
const serviceNameKey = "service_name"
 | 
			
		||||
const serviceNameKeyCSM = "csm.service_name"
 | 
			
		||||
const serviceNamespaceKey = "service_namespace"
 | 
			
		||||
const serviceNamespaceKeyCSM = "csm.service_namespace"
 | 
			
		||||
const serviceNameValue = "grpc-service"
 | 
			
		||||
const serviceNamespaceValue = "grpc-service-namespace"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -125,11 +127,11 @@ func (fsh *fakeStatsHandler) HandleRPC(ctx context.Context, rs stats.RPCStats) {
 | 
			
		|||
	// aren't started. All of these should have access to the desired telemetry
 | 
			
		||||
	// labels.
 | 
			
		||||
	case *stats.OutPayload, *stats.InPayload, *stats.End:
 | 
			
		||||
		if label, ok := fsh.labels.TelemetryLabels[serviceNameKey]; !ok || label != serviceNameValue {
 | 
			
		||||
			fsh.t.Fatalf("for telemetry label %v, want: %v, got: %v", serviceNameKey, serviceNameValue, label)
 | 
			
		||||
		if label, ok := fsh.labels.TelemetryLabels[serviceNameKeyCSM]; !ok || label != serviceNameValue {
 | 
			
		||||
			fsh.t.Fatalf("for telemetry label %v, want: %v, got: %v", serviceNameKeyCSM, serviceNameValue, label)
 | 
			
		||||
		}
 | 
			
		||||
		if label, ok := fsh.labels.TelemetryLabels[serviceNamespaceKey]; !ok || label != serviceNamespaceValue {
 | 
			
		||||
			fsh.t.Fatalf("for telemetry label %v, want: %v, got: %v", serviceNamespaceKey, serviceNamespaceValue, label)
 | 
			
		||||
		if label, ok := fsh.labels.TelemetryLabels[serviceNamespaceKeyCSM]; !ok || label != serviceNamespaceValue {
 | 
			
		||||
			fsh.t.Fatalf("for telemetry label %v, want: %v, got: %v", serviceNamespaceKeyCSM, serviceNamespaceValue, label)
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
	default:
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -89,12 +89,12 @@ func validateClusterAndConstructClusterUpdate(cluster *v3clusterpb.Cluster, serv
 | 
			
		|||
			if fields := val.GetFields(); fields != nil {
 | 
			
		||||
				if val, ok := fields["service_name"]; ok {
 | 
			
		||||
					if _, ok := val.GetKind().(*structpb.Value_StringValue); ok {
 | 
			
		||||
						telemetryLabels["service_name"] = val.GetStringValue()
 | 
			
		||||
						telemetryLabels["csm.service_name"] = val.GetStringValue()
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
				if val, ok := fields["service_namespace"]; ok {
 | 
			
		||||
					if _, ok := val.GetKind().(*structpb.Value_StringValue); ok {
 | 
			
		||||
						telemetryLabels["service_namespace"] = val.GetStringValue()
 | 
			
		||||
						telemetryLabels["csm.service_namespace"] = val.GetStringValue()
 | 
			
		||||
					}
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1363,8 +1363,8 @@ func (s) TestUnmarshalCluster(t *testing.T) {
 | 
			
		|||
				LRSServerConfig: &bootstrap.ServerConfig{ServerURI: "test-server-uri"},
 | 
			
		||||
				Raw:             v3ClusterAnyWithTelemetryLabels,
 | 
			
		||||
				TelemetryLabels: map[string]string{
 | 
			
		||||
					"service_name":      "grpc-service",
 | 
			
		||||
					"service_namespace": "grpc-service-namespace",
 | 
			
		||||
					"csm.service_name":      "grpc-service",
 | 
			
		||||
					"csm.service_namespace": "grpc-service-namespace",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
| 
						 | 
				
			
			@ -1379,7 +1379,7 @@ func (s) TestUnmarshalCluster(t *testing.T) {
 | 
			
		|||
				LRSServerConfig: &bootstrap.ServerConfig{ServerURI: "test-server-uri"},
 | 
			
		||||
				Raw:             v3ClusterAnyWithTelemetryLabelsIgnoreSome,
 | 
			
		||||
				TelemetryLabels: map[string]string{
 | 
			
		||||
					"service_name": "grpc-service",
 | 
			
		||||
					"csm.service_name": "grpc-service",
 | 
			
		||||
				},
 | 
			
		||||
			},
 | 
			
		||||
		},
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in New Issue