snapshot of https://github.com/grpc/grpc-go.git (master, 2026-09-15; blobs > 131072B omitted)
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
Some checks failed
codecov / upload (push) Failing after 13m59s
CodeQL / Analyze (push) Failing after 1s
govulncheck / govulncheck (.) (push) Failing after 1s
govulncheck / govulncheck (./cmd/protoc-gen-go-grpc) (push) Failing after 1s
govulncheck / govulncheck (./gcp/observability) (push) Failing after 1s
govulncheck / govulncheck (./security/advancedtls) (push) Failing after 1s
govulncheck / govulncheck (./stats/opencensus) (push) Failing after 1s
Testing / tests (arm, latest) (push) Waiting to run
Testing / vet-proto (push) Failing after 1s
Testing / extras (latest) (push) Failing after 1s
Testing / static checks (latest-1) (push) Failing after 1s
Testing / tests (-race, latest) (push) Failing after 1s
Testing / tests (i386, latest) (push) Failing after 1s
Testing / tests (latest) (push) Failing after 1s
Testing / tests (latest-1) (push) Failing after 1s
Lock Threads / lock (push) Failing after 1s
Stale bot / stale (push) Failing after 1s
This commit is contained in:
490
stats/opentelemetry/csm/pluginoption_test.go
Normal file
490
stats/opentelemetry/csm/pluginoption_test.go
Normal file
@@ -0,0 +1,490 @@
|
||||
/*
|
||||
*
|
||||
* 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/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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
meshIDPopulated bool
|
||||
localLabelsWant map[string]string
|
||||
metadataExchangeLabelsWant map[string]string
|
||||
}{
|
||||
{
|
||||
name: "no-type",
|
||||
csmCanonicalServiceNamePopulated: true,
|
||||
meshIDPopulated: 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.meshIDPopulated {
|
||||
os.Setenv("CSM_MESH_ID", "mesh_id")
|
||||
defer os.Unsetenv("CSM_MESH_ID")
|
||||
}
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user